From 9327da866241acb17b9d2a320c8c3ba32817779f Mon Sep 17 00:00:00 2001 From: DevRozaDev <158298065+DevRozaDev@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:46:07 +0200 Subject: [PATCH 1/2] feature package --- protocol/Cargo.lock | 368 ++++----- protocol/package.json | 8 +- protocol/programs/invariant/Cargo.toml | 3 + protocol/programs/invariant/src/errors.rs | 2 +- .../src/instructions/change_liquidity.rs | 283 +++++++ .../src/instructions/create_position.rs | 1 + .../invariant/src/instructions/mod.rs | 2 + .../src/instructions/remove_position.rs | 7 +- protocol/programs/invariant/src/lib.rs | 16 + protocol/programs/protocol/Cargo.toml | 3 + .../programs/protocol/src/contexts/burn.rs | 207 ++--- .../protocol/src/contexts/init_pool.rs | 25 +- .../programs/protocol/src/contexts/mint.rs | 173 ++-- protocol/programs/protocol/src/lib.rs | 9 +- protocol/programs/protocol/src/math.rs | 173 ++-- .../programs/protocol/src/states/lp_pool.rs | 4 +- protocol/sdk/package-lock.json | 4 +- protocol/sdk/package.json | 2 +- protocol/sdk/src/idl/protocol.ts | 92 +-- protocol/sdk/src/math.ts | 12 +- protocol/sdk/src/protocol.ts | 216 +++-- protocol/sdk/src/types.ts | 41 +- protocol/tests.sh | 7 +- protocol/tests/burn.test.ts | 237 +++--- protocol/tests/init-lp-pool.test.ts | 32 +- protocol/tests/init.test.ts | 12 +- protocol/tests/math.test.ts | 3 +- protocol/tests/mint-high-tick-big.test.ts | 250 ++++++ protocol/tests/mint-high-tick-small.test.ts | 250 ++++++ protocol/tests/mint-zero-tick-big.test.ts | 250 ++++++ protocol/tests/mint-zero-tick-small.test.ts | 250 ++++++ protocol/tests/mint.test.ts | 288 ------- protocol/tests/multi-pool.test.ts | 758 ++++++++++++++++++ 33 files changed, 2938 insertions(+), 1050 deletions(-) create mode 100644 protocol/programs/invariant/src/instructions/change_liquidity.rs create mode 100644 protocol/tests/mint-high-tick-big.test.ts create mode 100644 protocol/tests/mint-high-tick-small.test.ts create mode 100644 protocol/tests/mint-zero-tick-big.test.ts create mode 100644 protocol/tests/mint-zero-tick-small.test.ts delete mode 100644 protocol/tests/mint.test.ts create mode 100644 protocol/tests/multi-pool.test.ts diff --git a/protocol/Cargo.lock b/protocol/Cargo.lock index 8e08533..1dad1ed 100644 --- a/protocol/Cargo.lock +++ b/protocol/Cargo.lock @@ -56,6 +56,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy", @@ -233,9 +234,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "ark-bn254" @@ -356,15 +357,15 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "assert_matches" @@ -493,16 +494,6 @@ dependencies = [ "hashbrown 0.13.2", ] -[[package]] -name = "borsh" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" -dependencies = [ - "borsh-derive 1.5.1", - "cfg_aliases", -] - [[package]] name = "borsh-derive" version = "0.9.3" @@ -529,20 +520,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "borsh-derive" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" -dependencies = [ - "once_cell", - "proc-macro-crate 3.1.0", - "proc-macro2", - "quote", - "syn 2.0.74", - "syn_derive", -] - [[package]] name = "borsh-derive-internal" version = "0.9.3" @@ -620,22 +597,22 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.16.3" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" +checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -646,12 +623,13 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.1.10" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" +checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -660,12 +638,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "chrono" version = "0.4.38" @@ -706,15 +678,15 @@ dependencies = [ [[package]] name = "constant_time_eq" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -814,7 +786,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -825,7 +797,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -1102,9 +1074,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -1129,6 +1101,7 @@ dependencies = [ "decimal", "integer-sqrt", "num-traits", + "solana-program", "uint", ] @@ -1158,9 +1131,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -1182,9 +1155,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libsecp256k1" @@ -1236,13 +1209,12 @@ dependencies = [ [[package]] name = "light-poseidon" -version = "0.2.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +checksum = "a5b439809cdfc0d86ecc7317f1724df13dfa665df48991b79e90e689411451f7" dependencies = [ "ark-bn254", "ark-ff", - "num-bigint", "thiserror", ] @@ -1308,6 +1280,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-derive" version = "0.4.2" @@ -1316,7 +1299,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -1337,13 +1320,34 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_enum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +dependencies = [ + "num_enum_derive 0.6.1", +] + [[package]] name = "num_enum" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ - "num_enum_derive", + "num_enum_derive 0.7.3", +] + +[[package]] +name = "num_enum_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.77", ] [[package]] @@ -1352,10 +1356,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -1455,34 +1459,21 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "version_check", + "once_cell", + "toml_edit 0.19.15", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro-crate" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "toml_edit 0.22.21", ] [[package]] @@ -1503,6 +1494,7 @@ dependencies = [ "bytemuck", "decimal", "invariant", + "solana-program", ] [[package]] @@ -1522,14 +1514,14 @@ checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1636,9 +1628,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ "bitflags", ] @@ -1680,9 +1672,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] @@ -1713,9 +1705,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.206" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -1731,20 +1723,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.206" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.124" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -1771,7 +1763,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -1821,16 +1813,16 @@ dependencies = [ ] [[package]] -name = "signature" -version = "1.6.4" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "siphasher" -version = "0.3.11" +name = "signature" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "sized-chunks" @@ -1850,13 +1842,17 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "solana-frozen-abi" -version = "1.18.22" +version = "1.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20a6ef2db80dceb124b7bf81cca3300804bf427d2711973fc3df450ed7dfb26d" +checksum = "06e33cbcaa4fb22729560b1f5078d809a81a7d54e8b4a0d8c0484cc419d1fc2f" dependencies = [ + "ahash 0.8.11", + "blake3", "block-buffer 0.10.4", "bs58 0.4.0", "bv", + "byteorder", + "cc", "either", "generic-array", "im", @@ -1867,6 +1863,7 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", + "serde_json", "sha2 0.10.8", "solana-frozen-abi-macro", "subtle", @@ -1875,21 +1872,21 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" -version = "1.18.22" +version = "1.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70088de7d4067d19a7455609e2b393e6086bd847bb39c4d2bf234fc14827ef9e" +checksum = "29b21f4329e2ee2d0a3f948ba300407b8bf2055308ccc0c9afb2eca44cb1a322" dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] name = "solana-logger" -version = "1.18.22" +version = "1.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b129da15193f26db62d62ae6bb9f72361f361bcdc36054be3ab8bc04cc7a4f31" +checksum = "cf2142caa78bdc817e6f6b2a3d36a1d4e0338429b127c5fd4c184925c5e13991" dependencies = [ "env_logger", "lazy_static", @@ -1898,9 +1895,9 @@ dependencies = [ [[package]] name = "solana-program" -version = "1.18.22" +version = "1.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb2b2c8babfae4cace1a25b6efa00418f3acd852cf55d7cecc0360d3c5050479" +checksum = "71109871717258d6138b8e344e54958c768f14fe35153a963bd817b1c142fa79" dependencies = [ "ark-bn254", "ark-ec", @@ -1912,7 +1909,6 @@ dependencies = [ "blake3", "borsh 0.10.3", "borsh 0.9.3", - "borsh 1.5.1", "bs58 0.4.0", "bv", "bytemuck", @@ -1930,7 +1926,7 @@ dependencies = [ "log", "memoffset", "num-bigint", - "num-derive", + "num-derive 0.3.3", "num-traits", "parking_lot", "rand 0.8.5", @@ -1953,15 +1949,15 @@ dependencies = [ [[package]] name = "solana-sdk" -version = "1.18.22" +version = "1.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e0f0def5c5af07f53d321cea7b104487b522cfff77c3cae3da361bfe956e9e" +checksum = "fa9856edf27de2d32b91dc4faf08852e2231cc6b127337d71b34c43f248bf832" dependencies = [ "assert_matches", "base64 0.21.7", "bincode", "bitflags", - "borsh 1.5.1", + "borsh 0.10.3", "bs58 0.4.0", "bytemuck", "byteorder", @@ -1978,9 +1974,9 @@ dependencies = [ "libsecp256k1", "log", "memmap2", - "num-derive", + "num-derive 0.3.3", "num-traits", - "num_enum", + "num_enum 0.6.1", "pbkdf2 0.11.0", "qstring", "qualifier_attr", @@ -1995,7 +1991,6 @@ dependencies = [ "serde_with", "sha2 0.10.8", "sha3 0.10.8", - "siphasher", "solana-frozen-abi", "solana-frozen-abi-macro", "solana-logger", @@ -2008,15 +2003,15 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "1.18.22" +version = "1.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55c196c8050834c391a34b58e3c9fd86b15452ef1feeeafa1dbeb9d2291dfec" +checksum = "7cce3b1aceaaf92c82d9ea3e7326d1c7dce4dd7bc45421be8d3834f275ab464f" dependencies = [ "bs58 0.4.0", "proc-macro2", "quote", "rustversion", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -2027,9 +2022,9 @@ checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" [[package]] name = "solana-zk-token-sdk" -version = "1.18.22" +version = "1.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee07fa523b4cfcff68de774db7aa87d2da2c4357155a90bacd9a0a0af70a99" +checksum = "ac110323e0eaf7b2d1764ab35efbb8c17d40fbcfaecbd0829c025e357a6318f6" dependencies = [ "aes-gcm-siv", "base64 0.21.7", @@ -2041,7 +2036,7 @@ dependencies = [ "itertools", "lazy_static", "merlin", - "num-derive", + "num-derive 0.3.3", "num-traits", "rand 0.7.3", "serde", @@ -2062,7 +2057,7 @@ checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f" dependencies = [ "assert_matches", "borsh 0.10.3", - "num-derive", + "num-derive 0.4.2", "num-traits", "solana-program", "spl-token", @@ -2089,7 +2084,7 @@ checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93" dependencies = [ "quote", "spl-discriminator-syn", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -2101,15 +2096,15 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.74", + "syn 2.0.77", "thiserror", ] [[package]] name = "spl-memo" -version = "4.0.4" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49f49f95f2d02111ded31696ab38a081fab623d4c76bd4cb074286db4560836" +checksum = "f0f180b03318c3dbab3ef4e1e4d46d5211ae3c780940dd0a28695aba4b59a75a" dependencies = [ "solana-program", ] @@ -2133,7 +2128,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "249e0318493b6bcf27ae9902600566c689b7dfba9f1bdff5893e92253374e78c" dependencies = [ - "num-derive", + "num-derive 0.4.2", "num-traits", "solana-program", "spl-program-error-derive", @@ -2149,7 +2144,7 @@ dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -2182,15 +2177,15 @@ dependencies = [ [[package]] name = "spl-token" -version = "4.0.3" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9eb465e4bf5ce1d498f05204c8089378c1ba34ef2777ea95852fc53a1fd4fb2" +checksum = "08459ba1b8f7c1020b4582c4edf0f5c7511a5e099a7a97570c9698d4f2337060" dependencies = [ "arrayref", "bytemuck", - "num-derive", + "num-derive 0.3.3", "num-traits", - "num_enum", + "num_enum 0.6.1", "solana-program", "thiserror", ] @@ -2203,9 +2198,9 @@ checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86" dependencies = [ "arrayref", "bytemuck", - "num-derive", + "num-derive 0.4.2", "num-traits", - "num_enum", + "num_enum 0.7.3", "solana-program", "solana-zk-token-sdk", "spl-memo", @@ -2225,9 +2220,9 @@ checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059" dependencies = [ "arrayref", "bytemuck", - "num-derive", + "num-derive 0.4.2", "num-traits", - "num_enum", + "num_enum 0.7.3", "solana-program", "solana-security-txt", "solana-zk-token-sdk", @@ -2344,27 +2339,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.74" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.74", -] - [[package]] name = "termcolor" version = "1.4.1" @@ -2391,7 +2374,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -2445,13 +2428,24 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", "toml_datetime", - "winnow", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.6.18", ] [[package]] @@ -2474,24 +2468,24 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "universal-hash" @@ -2533,34 +2527,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2568,28 +2563,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -2708,6 +2703,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -2726,7 +2730,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] [[package]] @@ -2746,5 +2750,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.77", ] diff --git a/protocol/package.json b/protocol/package.json index 9d556b8..704f38f 100644 --- a/protocol/package.json +++ b/protocol/package.json @@ -10,9 +10,13 @@ "test": "npm run build:all && npx ts-mocha -p tsconfig.json tests/**/*.ts", "test:init": "anchor test --skip-build tests/init.test.ts", "test:init-lp-pool": "anchor test --skip-build tests/init-lp-pool.test.ts", - "test:mint": "anchor test --skip-build tests/mint.test.ts", + "test:mint-zero-tick-small": "anchor test --skip-build tests/mint-zero-tick-small.test.ts", + "test:mint-zero-tick-big": "anchor test --skip-build tests/mint-zero-tick-big.test.ts", + "test:mint-high-tick-small": "anchor test --skip-build tests/mint-high-tick-small.test.ts", + "test:mint-high-tick-big": "anchor test --skip-build tests/mint-high-tick-big.test.ts", "test:burn": "anchor test --skip-build tests/burn.test.ts", - "test:math": "anchor test --skip-build tests/math.test.ts" + "test:math": "anchor test --skip-build tests/math.test.ts", + "test:multi-pool": "anchor test --skip-build tests/multi-pool.test.ts" }, "keywords": [], "author": "", diff --git a/protocol/programs/invariant/Cargo.toml b/protocol/programs/invariant/Cargo.toml index e2f6b3f..0ecf216 100644 --- a/protocol/programs/invariant/Cargo.toml +++ b/protocol/programs/invariant/Cargo.toml @@ -21,8 +21,11 @@ all = [] [dependencies] decimal = { path = "decimal" } + +solana-program = "=1.17.6" anchor-lang = "0.29.0" anchor-spl = "0.29.0" + integer-sqrt = "0.1.5" uint = "0.9.1" num-traits = "0.2.14" diff --git a/protocol/programs/invariant/src/errors.rs b/protocol/programs/invariant/src/errors.rs index 51ef257..c1f4357 100644 --- a/protocol/programs/invariant/src/errors.rs +++ b/protocol/programs/invariant/src/errors.rs @@ -24,7 +24,7 @@ pub enum ErrorCode { InvalidTickLiquidity = 9, // 1779 #[msg("Disable empty position pokes")] EmptyPositionPokes = 10, // 177a - #[msg("Invalid tick liquidity")] + #[msg("Invalid position liquidity")] InvalidPositionLiquidity = 11, // 177b #[msg("Invalid pool liquidity")] InvalidPoolLiquidity = 12, // 177c diff --git a/protocol/programs/invariant/src/instructions/change_liquidity.rs b/protocol/programs/invariant/src/instructions/change_liquidity.rs new file mode 100644 index 0000000..6af2567 --- /dev/null +++ b/protocol/programs/invariant/src/instructions/change_liquidity.rs @@ -0,0 +1,283 @@ +use crate::interfaces::take_tokens::TakeTokens; +use crate::structs::pool::Pool; +use crate::structs::position::Position; +use crate::structs::tick::Tick; +use crate::ErrorCode::{self, *}; +use crate::*; +use anchor_lang::prelude::*; +use anchor_spl::token; +use anchor_spl::token_2022; +use anchor_spl::token_interface::TokenInterface; +use anchor_spl::token_interface::{Mint, TokenAccount}; +use decimals::*; +use interfaces::send_tokens::SendTokens; + +#[derive(Accounts)] +#[instruction( index: u32)] +pub struct ChangeLiquidity<'info> { + #[account(seeds = [b"statev1".as_ref()], bump = state.load()?.bump)] + pub state: AccountLoader<'info, State>, + #[account(mut, + seeds = [b"positionv1", + owner.key().as_ref(), + &index.to_le_bytes()], + bump = position.load()?.bump, + constraint = position.load()?.pool == pool.key() @ InvalidPositionIndex + )] + pub position: AccountLoader<'info, Position>, + #[account(mut, + seeds = [b"poolv1", token_x.key().as_ref(), token_y.key().as_ref(), &pool.load()?.fee.v.to_le_bytes(), &pool.load()?.tick_spacing.to_le_bytes()], + bump = pool.load()?.bump + )] + pub pool: AccountLoader<'info, Pool>, + #[account(mut)] + pub payer: Signer<'info>, + pub owner: Signer<'info>, + #[account(mut, + seeds = [b"tickv1", pool.key().as_ref(), &position.load()?.lower_tick_index.to_le_bytes()], + bump = lower_tick.load()?.bump + )] + pub lower_tick: AccountLoader<'info, Tick>, + #[account(mut, + seeds = [b"tickv1", pool.key().as_ref(), &position.load()?.upper_tick_index.to_le_bytes()], + bump = upper_tick.load()?.bump + )] + pub upper_tick: AccountLoader<'info, Tick>, + #[account(constraint = token_x.key() == pool.load()?.token_x @ InvalidTokenAccount, mint::token_program = token_x_program)] + pub token_x: InterfaceAccount<'info, Mint>, + #[account(constraint = token_y.key() == pool.load()?.token_y @ InvalidTokenAccount, mint::token_program = token_y_program)] + pub token_y: InterfaceAccount<'info, Mint>, + #[account(mut, + constraint = account_x.mint == token_x.key() @ InvalidMint, + constraint = &account_x.owner == owner.key @ InvalidOwner, + token::token_program = token_x_program, + )] + pub account_x: Box>, + #[account(mut, + constraint = account_y.mint == token_y.key() @ InvalidMint, + constraint = &account_y.owner == owner.key @ InvalidOwner, + token::token_program = token_y_program, + )] + pub account_y: Box>, + #[account(mut, + constraint = reserve_x.mint == token_x.key() @ InvalidMint, + constraint = &reserve_x.owner == program_authority.key @ InvalidOwner, + constraint = reserve_x.key() == pool.load()?.token_x_reserve @ InvalidTokenAccount, + token::token_program = token_x_program, + )] + pub reserve_x: Box>, + #[account(mut, + constraint = reserve_y.mint == token_y.key() @ InvalidMint, + constraint = &reserve_y.owner == program_authority.key @ InvalidOwner, + constraint = reserve_y.key() == pool.load()?.token_y_reserve @ InvalidTokenAccount, + token::token_program = token_y_program, + )] + pub reserve_y: Box>, + #[account(constraint = &state.load()?.authority == program_authority.key @ InvalidAuthority)] + /// CHECK: ignore + pub program_authority: AccountInfo<'info>, + pub token_x_program: Interface<'info, TokenInterface>, + pub token_y_program: Interface<'info, TokenInterface>, +} + +impl<'info> TakeTokens<'info> for ChangeLiquidity<'info> { + fn take_x(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> { + CpiContext::new( + self.token_x_program.to_account_info(), + token::Transfer { + from: self.account_x.to_account_info(), + to: self.reserve_x.to_account_info(), + authority: self.owner.to_account_info().clone(), + }, + ) + } + + fn take_y(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> { + CpiContext::new( + self.token_y_program.to_account_info(), + token::Transfer { + from: self.account_y.to_account_info(), + to: self.reserve_y.to_account_info(), + authority: self.owner.to_account_info().clone(), + }, + ) + } + + fn take_x_2022(&self) -> CpiContext<'_, '_, '_, 'info, token_2022::TransferChecked<'info>> { + CpiContext::new( + self.token_x_program.to_account_info(), + token_2022::TransferChecked { + mint: self.token_x.to_account_info(), + from: self.account_x.to_account_info(), + to: self.reserve_x.to_account_info(), + authority: self.owner.to_account_info().clone(), + }, + ) + } + + fn take_y_2022(&self) -> CpiContext<'_, '_, '_, 'info, token_2022::TransferChecked<'info>> { + CpiContext::new( + self.token_y_program.to_account_info(), + token_2022::TransferChecked { + mint: self.token_y.to_account_info(), + from: self.account_y.to_account_info(), + to: self.reserve_y.to_account_info(), + authority: self.owner.to_account_info().clone(), + }, + ) + } +} + +impl<'info> SendTokens<'info> for ChangeLiquidity<'info> { + fn send_x(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> { + CpiContext::new( + self.token_x_program.to_account_info(), + token::Transfer { + from: self.reserve_x.to_account_info(), + to: self.account_x.to_account_info(), + authority: self.program_authority.clone(), + }, + ) + } + + fn send_y(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> { + CpiContext::new( + self.token_y_program.to_account_info(), + token::Transfer { + from: self.reserve_y.to_account_info(), + to: self.account_y.to_account_info(), + authority: self.program_authority.clone(), + }, + ) + } + + fn send_x_2022(&self) -> CpiContext<'_, '_, '_, 'info, token_2022::TransferChecked<'info>> { + CpiContext::new( + self.token_x_program.to_account_info(), + token_2022::TransferChecked { + mint: self.token_x.to_account_info(), + from: self.reserve_x.to_account_info(), + to: self.account_x.to_account_info(), + authority: self.program_authority.clone(), + }, + ) + } + + fn send_y_2022(&self) -> CpiContext<'_, '_, '_, 'info, token_2022::TransferChecked<'info>> { + CpiContext::new( + self.token_y_program.to_account_info(), + token_2022::TransferChecked { + mint: self.token_y.to_account_info(), + from: self.reserve_y.to_account_info(), + to: self.account_y.to_account_info(), + authority: self.program_authority.clone(), + }, + ) + } +} + +impl<'info> ChangeLiquidity<'info> { + pub fn handler( + &self, + liquidity_delta: Liquidity, + add_liquidity: bool, + slippage_limit_lower: Price, + slippage_limit_upper: Price, + ) -> Result<()> { + msg!("INVARIANT: CHANGE POSITION LIQUIDITY"); + + let mut position = self.position.load_mut()?; + let pool = &mut self.pool.load_mut()?; + let lower_tick = &mut self.lower_tick.load_mut()?; + let upper_tick = &mut self.upper_tick.load_mut()?; + let current_timestamp = get_current_timestamp(); + let slot = get_current_slot(); + + let liquidity = position.liquidity; + require!( + add_liquidity || liquidity_delta != liquidity, + ErrorCode::PositionWithoutLiquidity + ); + + require!(liquidity_delta != Liquidity::new(0), ErrorCode::ZeroAmount); + + // validate price + let sqrt_price = pool.sqrt_price; + require!( + sqrt_price >= slippage_limit_lower, + ErrorCode::PriceLimitReached + ); + require!( + sqrt_price <= slippage_limit_upper, + ErrorCode::PriceLimitReached + ); + + position.seconds_per_liquidity_inside = calculate_seconds_per_liquidity_inside( + **lower_tick, + **upper_tick, + pool, + current_timestamp, + ); + position.last_slot = slot; + + let (amount_x, amount_y) = position.modify( + pool, + upper_tick, + lower_tick, + liquidity_delta, + add_liquidity, + current_timestamp, + )?; + + require!( + amount_x != TokenAmount::new(0) || amount_y != TokenAmount::new(0), + ErrorCode::ZeroOutput + ); + + if add_liquidity { + match self.token_x_program.key() { + token_2022::ID => token_2022::transfer_checked( + self.take_x_2022(), + amount_x.0, + self.token_x.decimals, + )?, + token::ID => token::transfer(self.take_x(), amount_x.0)?, + _ => return Err(ErrorCode::InvalidTokenProgram.into()), + }; + match self.token_y_program.key() { + token_2022::ID => token_2022::transfer_checked( + self.take_y_2022(), + amount_y.0, + self.token_y.decimals, + )?, + token::ID => token::transfer(self.take_y(), amount_y.0)?, + _ => return Err(ErrorCode::InvalidTokenProgram.into()), + }; + } else { + let state = self.state.load()?; + let signer: &[&[&[u8]]] = get_signer!(state.nonce); + + match self.token_x_program.key() { + token_2022::ID => token_2022::transfer_checked( + self.send_x_2022().with_signer(signer), + amount_x.0, + self.token_x.decimals, + )?, + token::ID => token::transfer(self.send_x().with_signer(signer), amount_x.0)?, + _ => return Err(ErrorCode::InvalidTokenProgram.into()), + }; + + match self.token_y_program.key() { + token_2022::ID => token_2022::transfer_checked( + self.send_y_2022().with_signer(signer), + amount_y.0, + self.token_y.decimals, + )?, + token::ID => token::transfer(self.send_y().with_signer(signer), amount_y.0)?, + _ => return Err(ErrorCode::InvalidTokenProgram.into()), + }; + } + + Ok(()) + } +} diff --git a/protocol/programs/invariant/src/instructions/create_position.rs b/protocol/programs/invariant/src/instructions/create_position.rs index 63f7fb9..e713d29 100644 --- a/protocol/programs/invariant/src/instructions/create_position.rs +++ b/protocol/programs/invariant/src/instructions/create_position.rs @@ -156,6 +156,7 @@ impl<'info> CreatePosition<'info> { bump: u8, ) -> Result<()> { msg!("INVARIANT: CREATE POSITION"); + let mut position = self.position.load_init()?; let mut pool = &mut self.pool.load_mut()?; let lower_tick = &mut self.lower_tick.load_mut()?; diff --git a/protocol/programs/invariant/src/instructions/mod.rs b/protocol/programs/invariant/src/instructions/mod.rs index 74493e7..cef5968 100644 --- a/protocol/programs/invariant/src/instructions/mod.rs +++ b/protocol/programs/invariant/src/instructions/mod.rs @@ -1,4 +1,5 @@ pub mod change_fee_receiver; +pub mod change_liquidity; pub mod change_protocol_fee; pub mod claim_fee; pub mod create_fee_tier; @@ -16,6 +17,7 @@ pub mod update_seconds_per_liquidity; pub mod withdraw_protocol_fee; pub use change_fee_receiver::*; +pub use change_liquidity::*; pub use change_protocol_fee::*; pub use claim_fee::*; pub use create_fee_tier::*; diff --git a/protocol/programs/invariant/src/instructions/remove_position.rs b/protocol/programs/invariant/src/instructions/remove_position.rs index 2a61b59..5e21e81 100644 --- a/protocol/programs/invariant/src/instructions/remove_position.rs +++ b/protocol/programs/invariant/src/instructions/remove_position.rs @@ -32,7 +32,7 @@ pub struct RemovePosition<'info> { )] pub position_list: AccountLoader<'info, PositionList>, #[account(mut, - close = owner, + close = payer, seeds = [b"positionv1", owner.key().as_ref(), &(position_list.load()?.head - 1).to_le_bytes()], @@ -62,6 +62,7 @@ pub struct RemovePosition<'info> { )] pub upper_tick: AccountLoader<'info, Tick>, #[account(mut)] + pub payer: Signer<'info>, pub owner: Signer<'info>, #[account(constraint = token_x.key() == pool.load()?.token_x @ InvalidTokenAccount, mint::token_program = token_x_program)] pub token_x: InterfaceAccount<'info, Mint>, @@ -198,7 +199,7 @@ impl<'info> RemovePosition<'info> { } close( self.lower_tick.to_account_info(), - self.owner.to_account_info(), + self.payer.to_account_info(), ) .unwrap(); @@ -211,7 +212,7 @@ impl<'info> RemovePosition<'info> { } close( self.upper_tick.to_account_info(), - self.owner.to_account_info(), + self.payer.to_account_info(), ) .unwrap(); diff --git a/protocol/programs/invariant/src/lib.rs b/protocol/programs/invariant/src/lib.rs index 43d15b0..4c0bc0a 100644 --- a/protocol/programs/invariant/src/lib.rs +++ b/protocol/programs/invariant/src/lib.rs @@ -96,6 +96,22 @@ pub mod invariant { .handler(index, lower_tick_index, upper_tick_index) } + pub fn change_liquidity( + ctx: Context, + _index: u32, + liquidity_delta: Liquidity, + add_liquidity: bool, + slippage_limit_lower: Price, + slippage_limit_upper: Price, + ) -> Result<()> { + ctx.accounts.handler( + liquidity_delta, + add_liquidity, + slippage_limit_lower, + slippage_limit_upper, + ) + } + pub fn transfer_position_ownership( ctx: Context, index: u32, diff --git a/protocol/programs/protocol/Cargo.toml b/protocol/programs/protocol/Cargo.toml index b9bd40e..0ae23b4 100644 --- a/protocol/programs/protocol/Cargo.toml +++ b/protocol/programs/protocol/Cargo.toml @@ -19,6 +19,9 @@ default = [] [dependencies] decimal = { path = "../invariant/decimal" } bytemuck = "1.16.0" + +solana-program = "=1.17.6" anchor-lang = {version = "0.29.0", features = ["init-if-needed"]} anchor-spl = "0.29.0" + invariant = { path = "../invariant", features = ["cpi"]} diff --git a/protocol/programs/protocol/src/contexts/burn.rs b/protocol/programs/protocol/src/contexts/burn.rs index 48e4891..039e730 100644 --- a/protocol/programs/protocol/src/contexts/burn.rs +++ b/protocol/programs/protocol/src/contexts/burn.rs @@ -1,9 +1,7 @@ use std::cell::RefMut; +use std::u32; -use crate::math::{ - calculate_amount_delta, compute_lp_share_change, - LiquidityChangeResult, -}; +use crate::math::{compute_lp_share_change, ComputeLpShareChangeResult}; use crate::states::{DerivedAccountIdentifier, LpPool, State, LP_TOKEN_IDENT}; use crate::{decimals::*, try_from}; use crate::{get_signer, ErrorCode::*}; @@ -15,10 +13,11 @@ use anchor_spl::{ token::{self}, token_2022, }; -use invariant::cpi::accounts::CreateTick; +use invariant::cpi::accounts::ChangeLiquidity; use invariant::decimals::Liquidity as InvLiquidity; +use invariant::structs::PositionList; use invariant::{ - cpi::accounts::{CreatePosition, RemovePosition}, + cpi::accounts::RemovePosition, structs::{Pool, Position}, }; @@ -40,8 +39,11 @@ pub struct BurnLpTokenCtx<'info> { bump=lp_pool.load()?.bump, )] pub lp_pool: AccountLoader<'info, LpPool>, + // validated right before removing the position + #[account(mut)] + pub last_position_lp_pool: AccountLoader<'info, LpPool>, #[account(mut, - // validated in the handler! + // validated in validate_token_lp // seeds = [LP_TOKEN_IDENT, token_x.key().as_ref(), token_y.key().as_ref(), &lp_pool.load()?.fee.v.to_le_bytes(), &lp_pool.load()?.tick_spacing.to_le_bytes()], // bump=lp_pool.load()?.token_bump, )] @@ -73,12 +75,12 @@ pub struct BurnLpTokenCtx<'info> { pub inv_state: UncheckedAccount<'info>, /// CHECK: invariant_program_authority is the authority of the Invariant program pub inv_program_authority: UncheckedAccount<'info>, - /// CHECK: might not exist, explicit check in the handler + /// CHECK: validated in the handler #[account(mut)] - pub position: AccountInfo<'info>, - /// CHECK: might not exist, check in Invariant CPI + pub position: AccountLoader<'info, Position>, + // validated right before removing the position #[account(mut)] - pub last_position: UncheckedAccount<'info>, + pub last_position: AccountLoader<'info, Position>, #[account(mut, // validated in the handler! // seeds = [INVARIANT_POOL_IDENT, token_x.key().as_ref(), token_y.key().as_ref(), &lp_pool.load()?.fee.v.to_le_bytes(), &lp_pool.load()?.tick_spacing.to_le_bytes()], @@ -90,9 +92,11 @@ pub struct BurnLpTokenCtx<'info> { // constraint = pool.load()?.fee.v == lp_pool.load()?.fee.v )] pub pool: AccountLoader<'info, Pool>, - /// CHECK: passed to Invariant - #[account(mut)] - pub position_list: UncheckedAccount<'info>, + #[account(mut, + seeds = [b"positionlistv1", program_authority.key().as_ref()], + seeds::program = invariant::ID, + bump = position_list.load()?.bump )] + pub position_list: AccountLoader<'info, PositionList>, /// CHECK: passed to Invariant #[account(mut)] pub lower_tick: UncheckedAccount<'info>, @@ -186,6 +190,7 @@ impl<'info> BurnLpTokenCtx<'info> { RemovePosition { state: self.inv_state.to_account_info(), program_authority: self.inv_program_authority.to_account_info(), + payer: self.owner.to_account_info(), owner: self.program_authority.to_account_info(), removed_position: self.position.to_account_info(), position_list: self.position_list.to_account_info(), @@ -206,52 +211,26 @@ impl<'info> BurnLpTokenCtx<'info> { ) } - pub fn create_tick( - &self, - account: AccountInfo<'info>, - ) -> CpiContext<'_, '_, '_, 'info, CreateTick<'info>> { + pub fn change_liquidity(&self) -> CpiContext<'_, '_, '_, 'info, ChangeLiquidity<'info>> { CpiContext::new( self.inv_program.to_account_info(), - CreateTick { - tick: account, - pool: self.pool.to_account_info(), - tickmap: self.tickmap.to_account_info(), - payer: self.owner.to_account_info(), - token_x: self.token_x.to_account_info(), - token_y: self.token_y.to_account_info(), - token_x_program: self.token_x_program.to_account_info(), - token_y_program: self.token_y_program.to_account_info(), - rent: self.rent.to_account_info(), - system_program: self.system_program.to_account_info(), - }, - ) - } - - pub fn create_position(&self) -> CpiContext<'_, '_, '_, 'info, CreatePosition<'info>> { - CpiContext::new( - self.inv_program.to_account_info(), - CreatePosition { + ChangeLiquidity { state: self.inv_state.to_account_info(), - // the previous last position was moved to the previous' address - position: self.last_position.to_account_info(), - pool: self.pool.to_account_info(), - position_list: self.position_list.to_account_info(), - payer: self.owner.to_account_info(), + program_authority: self.inv_program_authority.to_account_info(), owner: self.program_authority.to_account_info(), + payer: self.owner.to_account_info(), + position: self.position.to_account_info(), + pool: self.pool.to_account_info(), lower_tick: self.lower_tick.to_account_info(), upper_tick: self.upper_tick.to_account_info(), - tickmap: self.tickmap.to_account_info(), token_x: self.token_x.to_account_info(), token_y: self.token_y.to_account_info(), account_x: self.reserve_x.to_account_info(), account_y: self.reserve_y.to_account_info(), reserve_x: self.inv_reserve_x.to_account_info(), reserve_y: self.inv_reserve_y.to_account_info(), - program_authority: self.inv_program_authority.to_account_info(), token_x_program: self.token_x_program.to_account_info(), token_y_program: self.token_y_program.to_account_info(), - rent: self.rent.to_account_info(), - system_program: self.system_program.to_account_info(), }, ) } @@ -285,12 +264,43 @@ impl<'info> BurnLpTokenCtx<'info> { pub fn validate_position(&self) -> Result<()> { let lp_pool = &self.lp_pool.load()?; - require_keys_eq!(lp_pool.invariant_position, self.position.key()); + + let seeds = [ + b"positionv1", + self.program_authority.key.as_ref(), + &lp_pool.position_index.to_le_bytes(), + ]; + let (position_key, position_bump) = Pubkey::find_program_address(&seeds, &invariant::ID); + require_keys_eq!(position_key, self.position.key()); + require_eq!(position_bump, self.position.load()?.bump); Ok(()) } - pub fn process(&mut self, liquidity_delta: Liquidity, index: u32) -> Result<()> { + pub fn validate_last_position(&self) -> Result<()> { + if self.last_position_lp_pool.key() != self.lp_pool.key() { + require_eq!( + self.last_position_lp_pool.load()?.position_index, + self.position_list.load()?.head - 1 + ); + let seeds = [ + b"positionv1", + self.program_authority.key.as_ref(), + &self + .last_position_lp_pool + .load()? + .position_index + .to_le_bytes(), + ]; + let (position_key, position_bump) = + Pubkey::find_program_address(&seeds, &invariant::ID); + require_keys_eq!(position_key, self.last_position.key()); + require_eq!(position_bump, self.last_position.load()?.bump); + } + Ok(()) + } + + pub fn process(&mut self, liquidity_delta: Liquidity) -> Result<()> { self.validate_pool()?; self.validate_token_lp()?; self.validate_position()?; @@ -307,53 +317,72 @@ impl<'info> BurnLpTokenCtx<'info> { let lower_tick_index = position.lower_tick_index; let upper_tick_index = position.upper_tick_index; - let (amount_x, amount_y) = calculate_amount_delta( - Price::new(pool.sqrt_price.v), - current_liquidity, - false, - current_tick_index, - lower_tick_index, - upper_tick_index, - ) - .map_err(|_| InvalidShares)?; - let total_x = TokenAmount::new(lp_pool.leftover_x) + amount_x + fee_x; - let total_y = TokenAmount::new(lp_pool.leftover_y) + amount_y + fee_y; + let accumulated_x = TokenAmount::new(lp_pool.leftover_x) + fee_x; + let accumulated_y = TokenAmount::new(lp_pool.leftover_y) + fee_y; - let LiquidityChangeResult { - positions_details, - transferred_amounts, + let ComputeLpShareChangeResult { + liquidity_change, + mut transferred_amounts, lp_token_change, leftover_amounts, } = compute_lp_share_change( false, TokenAmount(self.token_lp.supply), liquidity_delta, - total_x, - total_y, + current_liquidity, + accumulated_x, + accumulated_y, lp_pool.tick_spacing, current_tick_index, Price::new(pool.sqrt_price.v), ) .unwrap(); - lp_pool.leftover_x = leftover_amounts.0.get(); - lp_pool.leftover_x = leftover_amounts.1.get(); - let lp_token_amount = + let lp_token_change = lp_token_change.expect("Lp change can't be zero when liquidity delta is not zero"); + let remove_position = lp_token_change.get() == self.token_lp.supply; + + if remove_position { + transferred_amounts.0 += leftover_amounts.0; + transferred_amounts.1 += leftover_amounts.1; + + lp_pool.leftover_x = 0; + lp_pool.leftover_y = 0; + } else { + lp_pool.leftover_x = leftover_amounts.0.get(); + lp_pool.leftover_y = leftover_amounts.1.get(); + } + let (transfer_x, transfer_y) = transferred_amounts; let signer: &[&[&[u8]]] = get_signer!(self.state.load()?.bump_authority); // burn lp token - token_2022::burn(self.burn_lp().with_signer(signer), lp_token_amount.get())?; + token_2022::burn(self.burn_lp().with_signer(signer), lp_token_change.get())?; + if remove_position { + self.validate_last_position()?; + invariant::cpi::remove_position( + self.remove_position().with_signer(signer), + lp_pool.position_index, + lower_tick_index, + upper_tick_index, + )?; - // TODO: move and track index inside of LpPool - invariant::cpi::remove_position( - self.remove_position().with_signer(signer), - index, - lower_tick_index, - upper_tick_index, - )?; + if self.last_position_lp_pool.key() != self.lp_pool.key() { + self.last_position_lp_pool.load_mut()?.position_index = lp_pool.position_index; + } + lp_pool.position_index = u32::MAX; + lp_pool.position_exists = false; + } else { + invariant::cpi::change_liquidity( + self.change_liquidity().with_signer(signer), + lp_pool.position_index, + InvLiquidity::new(liquidity_change.l.v), + liquidity_change.add, + pool.sqrt_price, + pool.sqrt_price, + )?; + } match self.token_x_program.key() { token_2022::ID => token_2022::transfer_checked( @@ -375,34 +404,6 @@ impl<'info> BurnLpTokenCtx<'info> { _ => return Err(InvalidTokenProgram.into()), }; - if positions_details.liquidity.v != 0 { - invariant::cpi::create_tick( - self.create_tick(self.lower_tick.to_account_info()), - lower_tick_index, - )?; - - invariant::cpi::create_tick( - self.create_tick(self.upper_tick.to_account_info()), - upper_tick_index, - )?; - - invariant::cpi::create_position( - self.create_position().with_signer(signer), - positions_details.lower_tick, - positions_details.upper_tick, - InvLiquidity::new(positions_details.liquidity.v), - pool.sqrt_price, - pool.sqrt_price, - )?; - - let new_position = try_from!(AccountLoader::, &self.last_position)?; - lp_pool.position_bump = new_position.load()?.bump; - lp_pool.invariant_position = self.last_position.key(); - } else { - lp_pool.position_bump = 0; - lp_pool.invariant_position = Pubkey::default(); - }; - Ok(()) } } diff --git a/protocol/programs/protocol/src/contexts/init_pool.rs b/protocol/programs/protocol/src/contexts/init_pool.rs index 7c831b2..8657a90 100644 --- a/protocol/programs/protocol/src/contexts/init_pool.rs +++ b/protocol/programs/protocol/src/contexts/init_pool.rs @@ -3,8 +3,7 @@ use crate::ErrorCode::*; use anchor_lang::prelude::*; use anchor_spl::associated_token::AssociatedToken; use anchor_spl::token_2022::Token2022; -use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; -use anchor_spl::{token, token_2022}; +use anchor_spl::token_interface::Mint; use decimal::Decimal; use invariant::structs::Pool; @@ -46,24 +45,6 @@ pub struct InitPoolCtx<'info> { pub pool: AccountLoader<'info, Pool>, pub token_x: InterfaceAccount<'info, Mint>, pub token_y: InterfaceAccount<'info, Mint>, - #[account(init_if_needed, - associated_token::mint = token_x, - associated_token::authority = program_authority, - associated_token::token_program = token_x_program, - payer = payer, - )] - pub reserve_x: InterfaceAccount<'info, TokenAccount>, - #[account(init_if_needed, - associated_token::mint = token_y, - associated_token::authority = program_authority, - associated_token::token_program = token_y_program, - payer = payer, - )] - pub reserve_y: InterfaceAccount<'info, TokenAccount>, - #[account(constraint = token_x_program.key() == token::ID || token_x_program.key() == token_2022::ID)] - pub token_x_program: Interface<'info, TokenInterface>, - #[account(constraint = token_y_program.key() == token::ID || token_y_program.key() == token_2022::ID)] - pub token_y_program: Interface<'info, TokenInterface>, pub token_program: Program<'info, Token2022>, pub associated_token_program: Program<'info, AssociatedToken>, pub system_program: Program<'info, System>, @@ -77,8 +58,8 @@ impl InitPoolCtx<'_> { let pool = &self.pool.load()?; **lp_pool = LpPool { - invariant_position: Pubkey::default(), - position_bump: 0, + position_index: u32::MAX, + position_exists: false, leftover_x: 0, leftover_y: 0, token_x, diff --git a/protocol/programs/protocol/src/contexts/mint.rs b/protocol/programs/protocol/src/contexts/mint.rs index a1dd34a..f50273d 100644 --- a/protocol/programs/protocol/src/contexts/mint.rs +++ b/protocol/programs/protocol/src/contexts/mint.rs @@ -1,5 +1,5 @@ use crate::decimals::{Liquidity, Price, TokenAmount}; -use crate::math::{calculate_amount_delta, compute_lp_share_change, get_max_tick, get_min_tick}; +use crate::math::{compute_lp_share_change, get_max_tick, get_min_tick}; use crate::states::{DerivedAccountIdentifier, LpPool, State, LP_TOKEN_IDENT}; use crate::{get_signer, ErrorCode::*}; use anchor_lang::prelude::*; @@ -11,9 +11,11 @@ use anchor_spl::{ token_2022, }; use decimal::{BetweenDecimals, Decimal}; +use invariant::cpi::accounts::{ChangeLiquidity, ClaimFee}; use invariant::decimals::{Liquidity as InvLiquidity, Price as InvPrice}; +use invariant::structs::PositionList; use invariant::{ - cpi::accounts::{CreatePosition, CreateTick, RemovePosition}, + cpi::accounts::{CreatePosition, CreateTick}, structs::{Pool, Position}, }; @@ -25,7 +27,6 @@ macro_rules! try_from { } const ADD: bool = true; -const SUBTRACT: bool = false; #[derive(Accounts)] pub struct MintLpTokenCtx<'info> { @@ -81,9 +82,6 @@ pub struct MintLpTokenCtx<'info> { /// CHECK: might not exist, explicit check in the handler #[account(mut)] pub position: AccountInfo<'info>, - /// CHECK: might not exist, check in Invariant CPI - #[account(mut)] - pub last_position: UncheckedAccount<'info>, #[account(mut, // validated in the handler! // seeds = [INVARIANT_POOL_IDENT, token_x.key().as_ref(), token_y.key().as_ref(), &lp_pool.load()?.fee.v.to_le_bytes(), &lp_pool.load()?.tick_spacing.to_le_bytes()], @@ -97,7 +95,7 @@ pub struct MintLpTokenCtx<'info> { pub pool: AccountLoader<'info, Pool>, /// CHECK: passed to Invariant #[account(mut)] - pub position_list: UncheckedAccount<'info>, + pub position_list: AccountLoader<'info, PositionList>, /// CHECK: passed to Invariant #[account(mut)] pub lower_tick: UncheckedAccount<'info>, @@ -187,18 +185,39 @@ impl<'info> MintLpTokenCtx<'info> { ) } - pub fn remove_position(&self) -> CpiContext<'_, '_, '_, 'info, RemovePosition<'info>> { + pub fn claim_fee(&self) -> CpiContext<'_, '_, '_, 'info, ClaimFee<'info>> { + CpiContext::new( + self.inv_program.to_account_info(), + ClaimFee { + state: self.inv_state.to_account_info(), + pool: self.pool.to_account_info(), + position: self.position.to_account_info(), + lower_tick: self.lower_tick.to_account_info(), + upper_tick: self.upper_tick.to_account_info(), + owner: self.program_authority.to_account_info(), + token_x: self.token_x.to_account_info(), + token_y: self.token_y.to_account_info(), + account_x: self.reserve_x.to_account_info(), + account_y: self.reserve_y.to_account_info(), + reserve_x: self.inv_reserve_x.to_account_info(), + reserve_y: self.inv_reserve_y.to_account_info(), + program_authority: self.inv_program_authority.to_account_info(), + token_x_program: self.token_x_program.to_account_info(), + token_y_program: self.token_y_program.to_account_info(), + }, + ) + } + + pub fn change_liquidity(&self) -> CpiContext<'_, '_, '_, 'info, ChangeLiquidity<'info>> { CpiContext::new( self.inv_program.to_account_info(), - RemovePosition { + ChangeLiquidity { state: self.inv_state.to_account_info(), program_authority: self.inv_program_authority.to_account_info(), owner: self.program_authority.to_account_info(), - removed_position: self.position.to_account_info(), - position_list: self.position_list.to_account_info(), - last_position: self.last_position.to_account_info(), + payer: self.owner.to_account_info(), + position: self.position.to_account_info(), pool: self.pool.to_account_info(), - tickmap: self.tickmap.to_account_info(), lower_tick: self.lower_tick.to_account_info(), upper_tick: self.upper_tick.to_account_info(), token_x: self.token_x.to_account_info(), @@ -218,8 +237,7 @@ impl<'info> MintLpTokenCtx<'info> { self.inv_program.to_account_info(), CreatePosition { state: self.inv_state.to_account_info(), - // the previous last position was moved to the previous' address - position: self.last_position.to_account_info(), + position: self.position.to_account_info(), pool: self.pool.to_account_info(), position_list: self.position_list.to_account_info(), payer: self.owner.to_account_info(), @@ -293,23 +311,28 @@ impl<'info> MintLpTokenCtx<'info> { pub fn validate_position(&self) -> Result<()> { let lp_pool = &self.lp_pool.load()?; - if lp_pool.invariant_position != Pubkey::default() { + if lp_pool.position_exists { let upper_tick_index = get_max_tick(lp_pool.tick_spacing); let lower_tick_index = get_min_tick(lp_pool.tick_spacing); - let position = try_from!(AccountLoader::, &self.position)?; - require_eq!(position.load()?.upper_tick_index, upper_tick_index); - require_eq!(position.load()?.lower_tick_index, lower_tick_index); - // explicitly support only one pool till more positions (pools) are supported - require_keys_eq!(self.position.key(), self.last_position.key()); - let owner = self.program_authority.key(); - let seeds = [b"positionv1", owner.as_ref(), &0i32.to_le_bytes()]; - let (pubkey, _bump) = Pubkey::find_program_address(&seeds, &invariant::ID); - require_keys_eq!(self.last_position.key(), pubkey); + let position_loader = try_from!(AccountLoader::, &self.position)?; + let position = position_loader.load()?; + require_eq!(position.upper_tick_index, upper_tick_index); + require_eq!(position.lower_tick_index, lower_tick_index); + + let seeds = [ + b"positionv1", + self.program_authority.key.as_ref(), + &lp_pool.position_index.to_le_bytes(), + ]; + let (position_key, position_bump) = + Pubkey::find_program_address(&seeds, &invariant::ID); + require_keys_eq!(position_key, self.position.key()); + require_eq!(position_bump, position.bump); } Ok(()) } - pub fn process(&mut self, liquidity: Liquidity, index: u32) -> Result<()> { + pub fn process(&mut self, liquidity: Liquidity) -> Result<()> { self.validate_pool()?; self.validate_token_lp()?; self.validate_position()?; @@ -323,49 +346,30 @@ impl<'info> MintLpTokenCtx<'info> { let current_sqrt_price = Price::new(sqrt_price.v); let upper_tick_index = get_max_tick(lp_pool.tick_spacing); let lower_tick_index = get_min_tick(lp_pool.tick_spacing); - let tick_spacing = lp_pool.tick_spacing; - msg!("tick_spacing: {}", tick_spacing); - msg!("upper_tick_index: {}", upper_tick_index); - msg!("lower_tick_index: {}", lower_tick_index); - let (unclaimed_fee_x, unclaimed_fee_y, (position_x, position_y)) = - if lp_pool.invariant_position != Pubkey::default() { - let position = *try_from!(AccountLoader::, &self.position)?.load()?; - let liquidity = Liquidity::new(position.liquidity.v); - let tokens_owed_x = TokenAmount::from_decimal(position.tokens_owed_x); - let tokens_owed_y = TokenAmount::from_decimal(position.tokens_owed_y); - let position_tokens = calculate_amount_delta( - current_sqrt_price, - liquidity, - SUBTRACT, - current_tick_index, - lower_tick_index, - upper_tick_index, - ) - .unwrap(); - (tokens_owed_x, tokens_owed_y, position_tokens) - } else { - ( - TokenAmount::new(0), - TokenAmount::new(0), - (TokenAmount::new(0), TokenAmount::new(0)), - ) - }; + let (unclaimed_fee_x, unclaimed_fee_y, current_liquidity) = if lp_pool.position_exists { + let position = *try_from!(AccountLoader::, &self.position)?.load()?; + let tokens_owed_x = TokenAmount::from_decimal(position.tokens_owed_x); + let tokens_owed_y = TokenAmount::from_decimal(position.tokens_owed_y); + let liquidity = Liquidity::new(position.liquidity.v); + (tokens_owed_x, tokens_owed_y, liquidity) + } else { + (TokenAmount::new(0), TokenAmount::new(0), Liquidity::new(0)) + }; let shares = compute_lp_share_change( ADD, TokenAmount::new(self.token_lp.supply), liquidity, - TokenAmount::new(lp_pool.leftover_x) + position_x + unclaimed_fee_x, - TokenAmount::new(lp_pool.leftover_y) + position_y + unclaimed_fee_y, + current_liquidity, + TokenAmount::new(lp_pool.leftover_x) + unclaimed_fee_x, + TokenAmount::new(lp_pool.leftover_y) + unclaimed_fee_y, lp_pool.tick_spacing, current_tick_index, current_sqrt_price, ) .unwrap(); - msg!("shares: {:?}", shares); - let (deposited_x, deposited_y) = shares.transferred_amounts; let (leftover_x, leftover_y) = shares.leftover_amounts; lp_pool.leftover_x = leftover_x.0; @@ -391,49 +395,40 @@ impl<'info> MintLpTokenCtx<'info> { _ => return Err(InvalidTokenProgram.into()), }; - // close and reopen position with new amount + // update or open a position let signer: &[&[&[u8]]] = get_signer!(self.state.load()?.bump_authority); - if lp_pool.invariant_position != Pubkey::default() { - // TODO: move and track index inside of LpPool - invariant::cpi::remove_position( - self.remove_position().with_signer(signer), - index, - lower_tick_index, - upper_tick_index, - )?; - invariant::cpi::create_tick( - self.create_tick(self.lower_tick.to_account_info()), - lower_tick_index, - )?; + let added_liquidity = shares.liquidity_change.l.v; + if lp_pool.position_exists { + // TODO gas optimization: only claim fee if it will affect liquidity + if unclaimed_fee_x + unclaimed_fee_y > TokenAmount::new(0) { + invariant::cpi::claim_fee( + self.claim_fee().with_signer(signer), + lp_pool.position_index, + lower_tick_index, + upper_tick_index, + )?; + } - invariant::cpi::create_tick( - self.create_tick(self.upper_tick.to_account_info()), - upper_tick_index, + invariant::cpi::change_liquidity( + self.change_liquidity().with_signer(signer), + lp_pool.position_index, + InvLiquidity::new(added_liquidity), + ADD, + sqrt_price, + sqrt_price, )?; - } - - { - let new_liquidity = shares.positions_details.liquidity.v; - msg!("new_liquidity: {}", new_liquidity); - msg!("reserve x: {}", self.reserve_x.amount); - msg!("reserve y: {}", self.reserve_y.amount); - + } else { + lp_pool.position_index = self.position_list.load()?.head; + lp_pool.position_exists = true; invariant::cpi::create_position( self.create_position().with_signer(signer), lower_tick_index, upper_tick_index, - InvLiquidity::new(new_liquidity), + InvLiquidity::new(added_liquidity), InvPrice::new(sqrt_price.v), InvPrice::new(sqrt_price.v), )?; - - // TODO: adjust to support multiple pools - lp_pool.invariant_position = self.last_position.key(); - { - let new_position = try_from!(AccountLoader::, &self.last_position)?; - lp_pool.position_bump = new_position.load()?.bump; - } } // mint LP tokens for user mint_to( diff --git a/protocol/programs/protocol/src/lib.rs b/protocol/programs/protocol/src/lib.rs index 9be4910..6b1da7b 100644 --- a/protocol/programs/protocol/src/lib.rs +++ b/protocol/programs/protocol/src/lib.rs @@ -41,12 +41,11 @@ pub mod protocol { ctx.accounts.process(token_bump, bump) } - pub fn mint_lp_token(ctx: Context, liquidity: u128, index: u32) -> Result<()> { - ctx.accounts.process(Liquidity::new(liquidity), index) + pub fn mint_lp_token(ctx: Context, liquidity: u128) -> Result<()> { + ctx.accounts.process(Liquidity::new(liquidity)) } - pub fn burn_lp_token(ctx: Context, liquidity: u128, index: u32) -> Result<()> { - ctx.accounts - .process(Liquidity::new(liquidity), index) + pub fn burn_lp_token(ctx: Context, liquidity: u128) -> Result<()> { + ctx.accounts.process(Liquidity::new(liquidity)) } } diff --git a/protocol/programs/protocol/src/math.rs b/protocol/programs/protocol/src/math.rs index f7be328..d8fb548 100644 --- a/protocol/programs/protocol/src/math.rs +++ b/protocol/programs/protocol/src/math.rs @@ -27,15 +27,14 @@ pub struct SingleTokenLiquidity { } #[derive(Debug, Clone)] -pub struct PositionDetails { - pub lower_tick: i32, - pub upper_tick: i32, - pub liquidity: Liquidity, +pub struct LiquidityChange { + pub l: Liquidity, + pub add: bool, } #[derive(Debug, Clone)] -pub struct LiquidityChangeResult { - pub positions_details: PositionDetails, +pub struct ComputeLpShareChangeResult { + pub liquidity_change: LiquidityChange, pub transferred_amounts: (TokenAmount, TokenAmount), pub lp_token_change: Option, pub leftover_amounts: (TokenAmount, TokenAmount), @@ -641,93 +640,90 @@ pub fn compute_lp_share_change( provide_liquidity: bool, lp_token_supply: TokenAmount, liquidity_delta: Liquidity, - x_before: TokenAmount, //fee + reserve + amount - y_before: TokenAmount, //fee + reserve + amount + liquidity_before: Liquidity, + accumulated_x: TokenAmount, // unclaimed fee + reserve (leftovers) + accumulated_y: TokenAmount, // unclaimed fee + reserve (leftovers) tick_spacing: u16, current_tick_index: i32, current_sqrt_price: Price, -) -> TrackableResult { +) -> TrackableResult { let max_tick = get_max_tick(tick_spacing); let min_tick = get_min_tick(tick_spacing); - let (old_x, old_y, current_liquidity) = compute_max_liquidity_position( - x_before, - y_before, - min_tick, - max_tick, - current_tick_index, - current_sqrt_price, - )?; + let (accumulated_usable_x, accumulated_usable_y, accumulated_liquidity) = + compute_max_liquidity_position( + accumulated_x, + accumulated_y, + min_tick, + max_tick, + current_tick_index, + current_sqrt_price, + )?; - // since the second position doesn't have an error, - // the only way for leftovers to appear is from creating the initial position with fee - let leftover_x = x_before - old_x; - let leftover_y = y_before - old_y; + let leftover_x = accumulated_x + .checked_sub(accumulated_usable_x) + .unwrap_or_default(); + let leftover_y = accumulated_y + .checked_sub(accumulated_usable_y) + .unwrap_or_default(); if liquidity_delta.v == 0 { - return Ok(LiquidityChangeResult { - positions_details: PositionDetails { - lower_tick: min_tick, - upper_tick: max_tick, - liquidity: current_liquidity, + return Ok(ComputeLpShareChangeResult { + liquidity_change: LiquidityChange { + l: accumulated_liquidity, + add: true, }, lp_token_change: None, transferred_amounts: (TokenAmount::new(0), TokenAmount::new(0)), leftover_amounts: (leftover_x, leftover_y), }); } - - let new_liquidity = if provide_liquidity { - current_liquidity + let mut add_liquidity = true; + let liquidity_change = if provide_liquidity { + accumulated_liquidity .checked_add(liquidity_delta) .map_err(|_| err!(TrackableError::ADD))? } else { - current_liquidity - .checked_sub(liquidity_delta) - .map_err(|_| err!(TrackableError::SUB))? + if accumulated_liquidity >= liquidity_delta { + accumulated_liquidity + .checked_sub(liquidity_delta) + .map_err(|_| err!(TrackableError::SUB))? + } else { + add_liquidity = false; + liquidity_delta + .checked_sub(accumulated_liquidity) + .map_err(|_| err!(TrackableError::SUB))? + } }; - let new_position_amounts = calculate_amount_delta( + let (transferred_x, transferred_y) = calculate_amount_delta( current_sqrt_price, - new_liquidity, - true, + liquidity_delta, + provide_liquidity, current_tick_index, min_tick, max_tick, ) - .map_err(|_| err!("Failed to calculate new lp share token cost"))?; - - let (transferred_x, transferred_y) = if provide_liquidity { - ( - new_position_amounts.0 - old_x, - new_position_amounts.1 - old_y, - ) - } else { - ( - old_x - new_position_amounts.0, - old_y - new_position_amounts.1, - ) - }; - - if transferred_x == TokenAmount::new(0) && transferred_y == TokenAmount::new(0) { - Err(err!("Liquidity delta too small to create a deposit"))? - } + .map_err(|_| err!("Failed to calculate transferred token amount"))?; let lp_token_change = liquidity_to_lp_token_amount( lp_token_supply, - current_liquidity, + liquidity_before + accumulated_liquidity, liquidity_delta, !provide_liquidity, )?; + if transferred_x == TokenAmount::new(0) && transferred_y == TokenAmount::new(0) { + Err(err!("Liquidity delta too small to create a deposit"))? + } + if lp_token_change == TokenAmount::new(0) { Err(err!("Liquidity delta too small to change LpToken amount"))? } - Ok(LiquidityChangeResult { - positions_details: PositionDetails { - lower_tick: min_tick, - upper_tick: max_tick, - liquidity: new_liquidity, + Ok(ComputeLpShareChangeResult { + liquidity_change: LiquidityChange { + l: liquidity_change, + add: add_liquidity, }, lp_token_change: Some(lp_token_change), transferred_amounts: (transferred_x, transferred_y), @@ -812,6 +808,7 @@ mod test { true, TokenAmount(0), delta_liquidity, + Liquidity::new(0), TokenAmount::new(0), TokenAmount::new(0), 1, @@ -819,9 +816,9 @@ mod test { Price::from_integer(1), ) .unwrap(); - assert_eq!(val.positions_details.liquidity.v, ONE_LP_TOKEN as u128); + assert_eq!(val.liquidity_change.l.v, ONE_LP_TOKEN as u128); assert_eq!( - val.positions_details.liquidity, + val.liquidity_change.l, current_liquidity.l + delta_liquidity ); @@ -860,6 +857,7 @@ mod test { true, token_supply, delta_liquidity, + Liquidity::new(0), TokenAmount::new(1000), TokenAmount::new(1000), 1, @@ -867,9 +865,57 @@ mod test { Price::from_integer(1), ) .unwrap(); - assert_eq!(val.positions_details.liquidity.v, 1331825849); + assert_eq!(val.liquidity_change.l.v, 1331825849); + assert_eq!( + val.liquidity_change.l, + current_liquidity.l + delta_liquidity + ); + + assert_eq!(val.lp_token_change.unwrap(), TokenAmount::new(99)); + assert_eq!( + val.transferred_amounts, + (TokenAmount::new(187), TokenAmount::new(187)) + ); + assert_eq!( + val.leftover_amounts, + (TokenAmount::new(0), TokenAmount::new(0)) + ); + } + { + let delta_liquidity = Liquidity::new(ONE_LP_TOKEN as u128 * 100); + let current_liquidity = get_max_liquidity( + TokenAmount::new(200000000), + TokenAmount::new(200000000), + -get_max_tick(1), + get_max_tick(1), + Price::from_integer(1), + true, + ) + .unwrap(); + + let token_supply = liquidity_to_lp_token_amount( + TokenAmount::new(0), + Liquidity::new(0), + current_liquidity.l, + false, + ) + .unwrap(); + + let val = compute_lp_share_change( + true, + token_supply, + delta_liquidity, + Liquidity::new(0), + TokenAmount::new(200000000), + TokenAmount::new(200000000), + 1, + 0, + Price::from_integer(1), + ) + .unwrap(); + assert_eq!(val.liquidity_change.l.v, 224422339705851); assert_eq!( - val.positions_details.liquidity, + val.liquidity_change.l, current_liquidity.l + delta_liquidity ); @@ -889,6 +935,7 @@ mod test { false, TokenAmount::new(1), Liquidity::new(1), + Liquidity::new(0), TokenAmount::new(0), TokenAmount::new(0), 1, @@ -912,6 +959,7 @@ mod test { false, TokenAmount::new(1), current_liquidity.l - Liquidity::new(1), + Liquidity::new(0), TokenAmount::new(1), TokenAmount::new(1), 1, @@ -935,6 +983,7 @@ mod test { false, TokenAmount::new(217), current_liquidity.l, + Liquidity::new(0), TokenAmount::new(1000), TokenAmount::new(1000), 1, diff --git a/protocol/programs/protocol/src/states/lp_pool.rs b/protocol/programs/protocol/src/states/lp_pool.rs index 9dc5191..170040b 100644 --- a/protocol/programs/protocol/src/states/lp_pool.rs +++ b/protocol/programs/protocol/src/states/lp_pool.rs @@ -10,8 +10,8 @@ impl DerivedAccountIdentifier for LpPool { #[repr(packed)] #[derive(PartialEq, Default, Debug, InitSpace)] pub struct LpPool { - pub invariant_position: Pubkey, - pub position_bump: u8, + pub position_index: u32, + pub position_exists: bool, pub leftover_x: u64, pub leftover_y: u64, pub token_x: Pubkey, diff --git a/protocol/sdk/package-lock.json b/protocol/sdk/package-lock.json index 9d60662..87a6849 100644 --- a/protocol/sdk/package-lock.json +++ b/protocol/sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "@invariant-labs/eclipse-link-sdk", - "version": "0.1.5", + "version": "0.1.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@invariant-labs/eclipse-link-sdk", - "version": "0.1.5", + "version": "0.1.11", "license": "ISC", "dependencies": { "@coral-xyz/anchor": "^0.29.0", diff --git a/protocol/sdk/package.json b/protocol/sdk/package.json index dffde7b..22ad78c 100644 --- a/protocol/sdk/package.json +++ b/protocol/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@invariant-labs/eclipse-link-sdk", - "version": "0.1.5", + "version": "0.1.11", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { diff --git a/protocol/sdk/src/idl/protocol.ts b/protocol/sdk/src/idl/protocol.ts index d3b523b..7831ab3 100644 --- a/protocol/sdk/src/idl/protocol.ts +++ b/protocol/sdk/src/idl/protocol.ts @@ -81,26 +81,6 @@ export type Protocol = { "isMut": false, "isSigner": false }, - { - "name": "reserveX", - "isMut": true, - "isSigner": false - }, - { - "name": "reserveY", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenXProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenYProgram", - "isMut": false, - "isSigner": false - }, { "name": "tokenProgram", "isMut": false, @@ -195,11 +175,6 @@ export type Protocol = { "isMut": true, "isSigner": false }, - { - "name": "lastPosition", - "isMut": true, - "isSigner": false - }, { "name": "pool", "isMut": true, @@ -280,10 +255,6 @@ export type Protocol = { { "name": "liquidity", "type": "u128" - }, - { - "name": "index", - "type": "u32" } ] }, @@ -310,6 +281,11 @@ export type Protocol = { "isMut": true, "isSigner": false }, + { + "name": "lastPositionLpPool", + "isMut": true, + "isSigner": false + }, { "name": "tokenLp", "isMut": true, @@ -448,10 +424,6 @@ export type Protocol = { { "name": "liquidity", "type": "u128" - }, - { - "name": "index", - "type": "u32" } ] } @@ -463,12 +435,12 @@ export type Protocol = { "kind": "struct", "fields": [ { - "name": "invariantPosition", - "type": "publicKey" + "name": "positionIndex", + "type": "u32" }, { - "name": "positionBump", - "type": "u8" + "name": "positionExists", + "type": "bool" }, { "name": "leftoverX", @@ -708,26 +680,6 @@ export const IDL: Protocol = { "isMut": false, "isSigner": false }, - { - "name": "reserveX", - "isMut": true, - "isSigner": false - }, - { - "name": "reserveY", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenXProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenYProgram", - "isMut": false, - "isSigner": false - }, { "name": "tokenProgram", "isMut": false, @@ -822,11 +774,6 @@ export const IDL: Protocol = { "isMut": true, "isSigner": false }, - { - "name": "lastPosition", - "isMut": true, - "isSigner": false - }, { "name": "pool", "isMut": true, @@ -907,10 +854,6 @@ export const IDL: Protocol = { { "name": "liquidity", "type": "u128" - }, - { - "name": "index", - "type": "u32" } ] }, @@ -937,6 +880,11 @@ export const IDL: Protocol = { "isMut": true, "isSigner": false }, + { + "name": "lastPositionLpPool", + "isMut": true, + "isSigner": false + }, { "name": "tokenLp", "isMut": true, @@ -1075,10 +1023,6 @@ export const IDL: Protocol = { { "name": "liquidity", "type": "u128" - }, - { - "name": "index", - "type": "u32" } ] } @@ -1090,12 +1034,12 @@ export const IDL: Protocol = { "kind": "struct", "fields": [ { - "name": "invariantPosition", - "type": "publicKey" + "name": "positionIndex", + "type": "u32" }, { - "name": "positionBump", - "type": "u8" + "name": "positionExists", + "type": "bool" }, { "name": "leftoverX", diff --git a/protocol/sdk/src/math.ts b/protocol/sdk/src/math.ts index ac74bdd..e2df435 100644 --- a/protocol/sdk/src/math.ts +++ b/protocol/sdk/src/math.ts @@ -217,12 +217,22 @@ export const liquidityToLpTokenAmount = ( export const lpTokenAmountToLiquidity = ( lpTokenSupply: Decimal, currentLiquidity: Decimal, - lpTokenAmountDelta: Decimal + lpTokenAmountDelta: Decimal, + roundingUp: boolean ) => { if (lpTokenSupply.v.eq(new BN(0))) { return { v: lpTokenAmountDelta.v.muln(ONE_LP_TOKEN) }; } + if (roundingUp && lpTokenSupply.v !== new BN(0)) { + return { + v: lpTokenAmountDelta.v + .mul(currentLiquidity.v) + .add(lpTokenSupply.v.sub(new BN(1))) + .div(lpTokenSupply.v), + }; + } + return { v: lpTokenAmountDelta.v.mul(currentLiquidity.v).div(lpTokenSupply.v), }; diff --git a/protocol/sdk/src/protocol.ts b/protocol/sdk/src/protocol.ts index f3a447c..a07b7ef 100644 --- a/protocol/sdk/src/protocol.ts +++ b/protocol/sdk/src/protocol.ts @@ -25,9 +25,9 @@ import { } from "./consts"; import { ASSOCIATED_TOKEN_PROGRAM_ID, + createAssociatedTokenAccountIdempotentInstruction, getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID, - TOKEN_PROGRAM_ID, } from "@solana/spl-token"; import { IBurnLpToken, @@ -41,6 +41,7 @@ import { Market, Pair, } from "@invariant-labs/sdk-eclipse"; +import { getMaxTick, getMinTick } from "@invariant-labs/sdk-eclipse/lib/utils"; export class Protocol { public connection: Connection; @@ -134,18 +135,58 @@ export class Protocol { return await signAndSend(tx, signers, this.connection); } - async init(signer: Keypair): Promise { - const ix = await this.initIx(signer); - return await this.sendTx([ix], [signer]); + newReserveIfNoneIx( + token: PublicKey, + program_id: PublicKey, + signer?: Keypair + ): TransactionInstruction { + const reserveAddress = getAssociatedTokenAddressSync( + token, + this.programAuthority, + true, + program_id, + ASSOCIATED_TOKEN_PROGRAM_ID + ); + const payer = signer?.publicKey ?? this.wallet.publicKey; + return createAssociatedTokenAccountIdempotentInstruction( + payer, + reserveAddress, + this.programAuthority, + token, + program_id, + ASSOCIATED_TOKEN_PROGRAM_ID + ); + } + + newLpAccountIfNoneIx( + token: PublicKey, + signer?: Keypair + ): TransactionInstruction { + const payer = signer?.publicKey ?? this.wallet.publicKey; + + const lpAccountAddress = getAssociatedTokenAddressSync( + token, + payer, + undefined, + TOKEN_2022_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID + ); + return createAssociatedTokenAccountIdempotentInstruction( + payer, + lpAccountAddress, + payer, + token, + TOKEN_2022_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID + ); } - // TODO: Make this the only init function - async initWithPositionlist( + + async init( signer: Keypair, market: Market ): Promise { const ix = await this.initIx(signer); - // TODO: Fix the function in SDK To accept different payer and owner const { positionListAddress } = await market.getPositionListAddress( this.programAuthority ); @@ -192,14 +233,6 @@ export class Protocol { const pool = accounts.pool ?? (await pair.getAddress(new PublicKey(getMarketAddress(this.network)))); - const reserveX = this.getReserveAddress(pair.tokenX); - const reserveY = this.getReserveAddress(pair.tokenY); - const tokenXProgram = - accounts.tokenXProgram ?? - (await getTokenProgramAddress(this.connection, pair.tokenX)); - const tokenYProgram = - accounts.tokenYProgram ?? - (await getTokenProgramAddress(this.connection, pair.tokenY)); return await this.program.methods .initLpPool() @@ -212,10 +245,6 @@ export class Protocol { pool, tokenX: pair.tokenX, tokenY: pair.tokenY, - reserveX, - reserveY, - tokenXProgram, - tokenYProgram, tokenProgram: TOKEN_2022_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, @@ -226,29 +255,54 @@ export class Protocol { async mintLpToken(params: IMintLpToken, signer: Keypair) { const setCuIx = computeUnitsInstruction(1_400_000); - const ix = await this.mintLpTokenIx(params, signer); - return await this.sendTx([setCuIx, ix], [signer]); + + const tokenXProgram = + params.tokenXProgram ?? + (await getTokenProgramAddress(this.connection, params.pair.tokenX)); + const tokenYProgram = + params.tokenYProgram ?? + (await getTokenProgramAddress(this.connection, params.pair.tokenY)); + + const reserveXmaybeIx = this.newReserveIfNoneIx( + params.pair.tokenX, + tokenXProgram, + signer + ); + const reserveYmaybeIx = this.newReserveIfNoneIx( + params.pair.tokenY, + tokenYProgram, + signer + ); + + const [tokenLp] = this.getLpTokenAddressAndBump(params.pair); + const accountLpMaybeIx = this.newLpAccountIfNoneIx(tokenLp, signer); + + const ix = await this.mintLpTokenIx( + { tokenXProgram, tokenYProgram, ...params }, + signer + ); + return await this.sendTx( + [setCuIx, reserveXmaybeIx, reserveYmaybeIx, accountLpMaybeIx, ix], + [signer] + ); } async mintLpTokenIx( - { pair, liquidityDelta, index, ...accounts }: IMintLpToken, + { + pair, + invariant, + poolStructure, + liquidityDelta, + ...accounts + }: IMintLpToken, signer?: Keypair ) { const owner = signer?.publicKey ?? this.wallet.publicKey; const [lpPool] = this.getLpPoolAddressAndBump(pair); const [tokenLp] = this.getLpTokenAddressAndBump(pair); - const pool = - accounts.pool ?? - (await pair.getAddress(new PublicKey(getMarketAddress(this.network)))); const reserveX = this.getReserveAddress(pair.tokenX); const reserveY = this.getReserveAddress(pair.tokenY); - const tokenXProgram = - accounts.tokenXProgram ?? - (await getTokenProgramAddress(this.connection, pair.tokenX)); - const tokenYProgram = - accounts.tokenYProgram ?? - (await getTokenProgramAddress(this.connection, pair.tokenY)); const accountLp = getAssociatedTokenAddressSync( tokenLp, owner, @@ -257,8 +311,34 @@ export class Protocol { ASSOCIATED_TOKEN_PROGRAM_ID ); + const { + tokenXReserve: invReserveX, + tokenYReserve: invReserveY, + tickmap, + } = poolStructure ?? (await invariant.getPool(pair)); + + // TODO: After Eclipse marketplace sdk update this won't need async at all + const pool = await pair.getAddress( + new PublicKey(getMarketAddress(this.network)) + ); + + const { positionListAddress: positionList } = + await invariant.getPositionListAddress(this.programAuthority); + + const lowerTickIndex = getMinTick(pair.feeTier.tickSpacing!); + const upperTickIndex = getMaxTick(pair.feeTier.tickSpacing!); + + const { tickAddress: lowerTick } = await invariant.getTickAddress( + pair, + lowerTickIndex + ); + const { tickAddress: upperTick } = await invariant.getTickAddress( + pair, + upperTickIndex + ); + return await this.program.methods - .mintLpToken(liquidityDelta, index) + .mintLpToken(liquidityDelta) .accounts({ state: this.stateAddress, programAuthority: this.programAuthority, @@ -271,8 +351,15 @@ export class Protocol { tokenY: pair.tokenY, reserveX, reserveY, - tokenXProgram, - tokenYProgram, + invProgram: invariant.program.programId, + invProgramAuthority: invariant.programAuthority, + invState: invariant.stateAddress, + lowerTick, + upperTick, + invReserveX, + invReserveY, + tickmap, + positionList, tokenProgram: TOKEN_2022_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, @@ -288,24 +375,22 @@ export class Protocol { } async burnLpTokenIx( - { pair, liquidityDelta, index, ...accounts }: IBurnLpToken, + { + pair, + invariant, + poolStructure, + liquidityDelta, + ...accounts + }: IBurnLpToken, signer?: Keypair ) { const owner = signer?.publicKey ?? this.wallet.publicKey; const [lpPool] = this.getLpPoolAddressAndBump(pair); const [tokenLp] = this.getLpTokenAddressAndBump(pair); - const pool = - accounts.pool ?? - (await pair.getAddress(new PublicKey(getMarketAddress(this.network)))); const reserveX = this.getReserveAddress(pair.tokenX); const reserveY = this.getReserveAddress(pair.tokenY); - const tokenXProgram = - accounts.tokenXProgram ?? - (await getTokenProgramAddress(this.connection, pair.tokenX)); - const tokenYProgram = - accounts.tokenYProgram ?? - (await getTokenProgramAddress(this.connection, pair.tokenY)); + const accountLp = getAssociatedTokenAddressSync( tokenLp, owner, @@ -314,8 +399,40 @@ export class Protocol { ASSOCIATED_TOKEN_PROGRAM_ID ); + const { + tokenXReserve: invReserveX, + tokenYReserve: invReserveY, + tickmap, + } = poolStructure ?? (await invariant.getPool(pair)); + + const tokenXProgram = + accounts.tokenXProgram ?? + (await getTokenProgramAddress(this.connection, pair.tokenX)); + const tokenYProgram = + accounts.tokenYProgram ?? + (await getTokenProgramAddress(this.connection, pair.tokenY)); + // TODO: After Eclipse marketplace sdk update this won't need async at all + const pool = await pair.getAddress( + new PublicKey(getMarketAddress(this.network)) + ); + + const { positionListAddress: positionList } = + await invariant.getPositionListAddress(this.programAuthority); + + const lowerTickIndex = getMinTick(pair.feeTier.tickSpacing!); + const upperTickIndex = getMaxTick(pair.feeTier.tickSpacing!); + + const { tickAddress: lowerTick } = await invariant.getTickAddress( + pair, + lowerTickIndex + ); + const { tickAddress: upperTick } = await invariant.getTickAddress( + pair, + upperTickIndex + ); + return await this.program.methods - .burnLpToken(liquidityDelta, index) + .burnLpToken(liquidityDelta) .accounts({ state: this.stateAddress, programAuthority: this.programAuthority, @@ -330,6 +447,15 @@ export class Protocol { reserveY, tokenXProgram, tokenYProgram, + invProgram: invariant.program.programId, + invProgramAuthority: invariant.programAuthority, + invState: invariant.stateAddress, + lowerTick, + upperTick, + invReserveX, + invReserveY, + tickmap, + positionList, tokenProgram: TOKEN_2022_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, diff --git a/protocol/sdk/src/types.ts b/protocol/sdk/src/types.ts index 251a25b..22df5d4 100644 --- a/protocol/sdk/src/types.ts +++ b/protocol/sdk/src/types.ts @@ -1,16 +1,22 @@ import { BN } from "@coral-xyz/anchor"; import { Pair } from "@invariant-labs/sdk-eclipse"; -import { Decimal } from "@invariant-labs/sdk-eclipse/lib/market"; +import { + Decimal, + Market, + PoolStructure, +} from "@invariant-labs/sdk-eclipse/lib/market"; import { Keypair, PublicKey, Transaction } from "@solana/web3.js"; export interface LpPoolStructure { - invariantPosition: PublicKey; + positionIndex: number; + positionExists: boolean; leftoverX: BN; leftoverY: BN; tokenX: PublicKey; tokenY: PublicKey; tickSpacing: number; fee: Decimal; + tokenBump: number; bump: number; } @@ -29,24 +35,14 @@ export interface IInitLpPool { export interface IMintLpToken { // data pair: Pair; + invariant: Market; + poolStructure?: PoolStructure; // params - index: number; liquidityDelta: BN; // invariant accounts - invProgram: PublicKey; - invState: PublicKey; - pool?: PublicKey; position: PublicKey; - lastPosition: PublicKey; - positionList: PublicKey; - lowerTick: PublicKey; - upperTick: PublicKey; - tickmap: PublicKey; accountX: PublicKey; accountY: PublicKey; - invReserveX: PublicKey; - invReserveY: PublicKey; - invProgramAuthority: PublicKey; tokenXProgram?: PublicKey; tokenYProgram?: PublicKey; } @@ -54,24 +50,17 @@ export interface IMintLpToken { export interface IBurnLpToken { // data pair: Pair; + invariant: Market; + poolStructure?: PoolStructure; // params - index: number; liquidityDelta: BN; + // fullrange accounts + lastPositionLpPool: PublicKey; // invariant accounts - invProgram: PublicKey; - invState: PublicKey; - pool?: PublicKey; position: PublicKey; lastPosition: PublicKey; - positionList: PublicKey; - lowerTick: PublicKey; - upperTick: PublicKey; - tickmap: PublicKey; accountX: PublicKey; accountY: PublicKey; - invReserveX: PublicKey; - invReserveY: PublicKey; - invProgramAuthority: PublicKey; tokenXProgram?: PublicKey; tokenYProgram?: PublicKey; -} \ No newline at end of file +} diff --git a/protocol/tests.sh b/protocol/tests.sh index 54d14a3..5fd2069 100755 --- a/protocol/tests.sh +++ b/protocol/tests.sh @@ -2,10 +2,15 @@ set -e e2e_tests=( + "math" "init" "init-lp-pool" - "mint" "burn" + "mint-high-tick-big" + "mint-high-tick-small" + "mint-zero-tick-big" + "mint-zero-tick-small" + "multi-pool" ) # currenty, there are no unit tests diff --git a/protocol/tests/burn.test.ts b/protocol/tests/burn.test.ts index 5d2d1a7..be83a36 100644 --- a/protocol/tests/burn.test.ts +++ b/protocol/tests/burn.test.ts @@ -1,7 +1,7 @@ import { AnchorProvider, BN } from "@coral-xyz/anchor"; import { Network } from "../sdk/src/network"; import { Protocol } from "../sdk/src/protocol"; -import { Keypair } from "@solana/web3.js"; +import { Keypair, PublicKey } from "@solana/web3.js"; import { createTokenMint, initMarket, @@ -10,7 +10,8 @@ import { } from "./test-utils"; import { assert } from "chai"; import { - ASSOCIATED_TOKEN_PROGRAM_ID, + getAccount, + getAssociatedTokenAddressSync, getOrCreateAssociatedTokenAccount, mintTo, TOKEN_2022_PROGRAM_ID, @@ -66,7 +67,7 @@ describe("burn lp token", () => { await initMarket(market, [pair], owner, initTick); protocol = await Protocol.build(Network.LOCAL, walletAnchor, connection); - await protocol.initWithPositionlist(owner, market); + await protocol.init(owner, market); const lowerTickVars: CreateTick = { pair, @@ -116,49 +117,23 @@ describe("burn lp token", () => { }); it("test", async () => { - const userTokenXAccount = await getOrCreateAssociatedTokenAccount( - connection, - owner, + const userTokenXAccountAddress = getAssociatedTokenAddressSync( pair.tokenX, owner.publicKey ); - const userTokenYAccount = await getOrCreateAssociatedTokenAccount( - connection, - owner, + const userTokenYAccountAddress = getAssociatedTokenAddressSync( pair.tokenY, owner.publicKey ); - // TODO: make it support cases where the id is different from 0 -> multiple positions for user aka multiple LP pools const positionId = 0; - const lastPositionId = 0; const liquidityDelta = new BN(50000000); - const { address: stateAddress } = await market.getStateAddress(); - const poolAddress = await pair.getAddress(INVARIANT_ADDRESS); - const { positionListAddress: positionListAddress } = - await market.getPositionListAddress(protocol.programAuthority); const { positionAddress } = await market.getPositionAddress( protocol.programAuthority, positionId ); - const { positionAddress: lastPositionAddress } = - await market.getPositionAddress( - protocol.programAuthority, - lastPositionId - ); - const { tickAddress: lowerTickAddress } = await market.getTickAddress( - pair, - lowerTick - ); - const { tickAddress: upperTickAddress } = await market.getTickAddress( - pair, - upperTick - ); - const { tokenXReserve, tokenYReserve, tickmap } = await market.getPool( - pair - ); await protocol.initLpPool( { @@ -167,43 +142,35 @@ describe("burn lp token", () => { owner ); - const [tokenLp] = protocol.getLpTokenAddressAndBump(pair); - // getorCreate has the advantage since you can call it several times with no downsides - let accountLp = await getOrCreateAssociatedTokenAccount( - connection, - owner, - tokenLp, - owner.publicKey, - undefined, - undefined, - undefined, - TOKEN_2022_PROGRAM_ID, - ASSOCIATED_TOKEN_PROGRAM_ID - ); - await protocol.mintLpToken( { - liquidityDelta, pair, - index: positionId, - invProgram: INVARIANT_ADDRESS, - invState: stateAddress, + invariant: market, + + liquidityDelta, + position: positionAddress, - lastPosition: lastPositionAddress, - pool: poolAddress, - positionList: positionListAddress, - lowerTick: lowerTickAddress, - upperTick: upperTickAddress, - tickmap, - accountX: userTokenXAccount.address, - accountY: userTokenYAccount.address, - invReserveX: tokenXReserve, - invReserveY: tokenYReserve, - invProgramAuthority: market.programAuthority, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, }, owner ); + const protocolTokenXAccountAddress = getAssociatedTokenAddressSync( + pair.tokenX, + protocol.programAuthority, + true + ); + const protocolTokenYAccountAddress = getAssociatedTokenAddressSync( + pair.tokenY, + protocol.programAuthority, + true + ); + + const getTokenAccount = async (tokenAccount: PublicKey) => { + return await getAccount(connection, tokenAccount, undefined); + }; + const position = await market.getPosition( protocol.programAuthority, positionId @@ -214,77 +181,127 @@ describe("burn lp token", () => { const { positionAddress: lastPositionAddress2 } = await market.getPositionAddress(protocol.programAuthority, 0); - accountLp = await getOrCreateAssociatedTokenAccount( - connection, - owner, + const [tokenLp] = protocol.getLpTokenAddressAndBump(pair); + const accountLpAddress = getAssociatedTokenAddressSync( tokenLp, owner.publicKey, undefined, + TOKEN_2022_PROGRAM_ID + ); + let accountLp = await getAccount( + connection, + accountLpAddress, undefined, - undefined, - TOKEN_2022_PROGRAM_ID, - ASSOCIATED_TOKEN_PROGRAM_ID + TOKEN_2022_PROGRAM_ID ); + assert.equal(accountLp.amount, 23n); + assert.equal( + (await getTokenAccount(userTokenXAccountAddress)).amount, + 9999999950n + ); + assert.equal( + (await getTokenAccount(userTokenYAccountAddress)).amount, + 9999999950n + ); + + const positionCount = ( + await market.getPositionList(protocol.programAuthority) + ).head; + const lastPositionId = positionCount == 0 ? 0 : positionCount - 1; + const lastPosition = await market.getPosition( + protocol.programAuthority, + lastPositionId + ); + const lastPositionPool = await market.getPoolByAddress(lastPosition.pool); + const [lastPositionLpPool] = protocol.getLpPoolAddressAndBump( + new Pair(lastPositionPool.tokenX, lastPositionPool.tokenY, { + fee: new BN(lastPositionPool.fee.v), + tickSpacing: lastPositionPool.tickSpacing, + }) + ); + + console.log("burn 1"); await protocol.burnLpToken( { - liquidityDelta: new BN(46000747), pair, - index: positionId, - invProgram: INVARIANT_ADDRESS, - invState: stateAddress, + invariant: market, + + liquidityDelta: new BN(46000747), + + lastPositionLpPool, + position: positionAddress, lastPosition: lastPositionAddress2, - pool: poolAddress, - positionList: positionListAddress, - lowerTick: lowerTickAddress, - upperTick: upperTickAddress, - tickmap, - accountX: userTokenXAccount.address, - accountY: userTokenYAccount.address, - invReserveX: tokenXReserve, - invReserveY: tokenYReserve, - invProgramAuthority: market.programAuthority, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, }, owner ); - accountLp = await getOrCreateAssociatedTokenAccount( + accountLp = await getAccount( connection, - owner, - tokenLp, - owner.publicKey, - undefined, - undefined, + accountLpAddress, undefined, - TOKEN_2022_PROGRAM_ID, - ASSOCIATED_TOKEN_PROGRAM_ID + TOKEN_2022_PROGRAM_ID ); assert.equal(accountLp.amount, 1n); + assert.equal( + (await getTokenAccount(userTokenXAccountAddress)).amount, + 9999999996n + ); + assert.equal( + (await getTokenAccount(userTokenYAccountAddress)).amount, + 9999999996n + ); + + assert.equal( + (await getTokenAccount(protocolTokenXAccountAddress)).amount, + 0n + ); + assert.equal( + (await getTokenAccount(protocolTokenYAccountAddress)).amount, + 0n + ); + console.log("burn 2"); + const positionBeforeLastBurn = await market.getPosition( + protocol.programAuthority, + 0 + ); await protocol.burnLpToken( { - liquidityDelta: new BN(2000030), pair, - index: positionId, - invProgram: INVARIANT_ADDRESS, - invState: stateAddress, + invariant: market, + + liquidityDelta: positionBeforeLastBurn.liquidity.v, + + lastPositionLpPool, + position: positionAddress, lastPosition: lastPositionAddress2, - pool: poolAddress, - positionList: positionListAddress, - lowerTick: lowerTickAddress, - upperTick: upperTickAddress, - tickmap, - accountX: userTokenXAccount.address, - accountY: userTokenYAccount.address, - invReserveX: tokenXReserve, - invReserveY: tokenYReserve, - invProgramAuthority: market.programAuthority, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, }, owner ); - let err; + assert.equal( + (await getTokenAccount(userTokenXAccountAddress)).amount, + 9999999999n + ); + assert.equal( + (await getTokenAccount(userTokenYAccountAddress)).amount, + 9999999999n + ); + assert.equal( + (await getTokenAccount(protocolTokenXAccountAddress)).amount, + 0n + ); + assert.equal( + (await getTokenAccount(protocolTokenYAccountAddress)).amount, + 0n + ); + let err = false; try { const positionAfterBurn = await market.getPosition( protocol.programAuthority, @@ -295,17 +312,15 @@ describe("burn lp token", () => { err = true; } assert(err, "burn did not remove position"); - accountLp = await getOrCreateAssociatedTokenAccount( + const lpPool = await protocol.getLpPool(pair); + accountLp = await getAccount( connection, - owner, - tokenLp, - owner.publicKey, - undefined, - undefined, + accountLpAddress, undefined, - TOKEN_2022_PROGRAM_ID, - ASSOCIATED_TOKEN_PROGRAM_ID + TOKEN_2022_PROGRAM_ID ); assert.equal(accountLp.amount, 0n); + assert.equal(lpPool.leftoverX, 0n); + assert.equal(lpPool.leftoverX, 0n); }); }); diff --git a/protocol/tests/init-lp-pool.test.ts b/protocol/tests/init-lp-pool.test.ts index 7e92642..235fce5 100644 --- a/protocol/tests/init-lp-pool.test.ts +++ b/protocol/tests/init-lp-pool.test.ts @@ -41,9 +41,6 @@ describe("init lp-pool", () => { giveSOL.map((account) => requestAirdrop(connection, account, 1e14)) ); - protocol = await Protocol.build(Network.LOCAL, walletAnchor, connection); - protocol.init(owner); - market = await Market.build( Network.LOCAL, walletAnchor, @@ -51,6 +48,9 @@ describe("init lp-pool", () => { INVARIANT_ADDRESS ); + protocol = await Protocol.build(Network.LOCAL, walletAnchor, connection); + protocol.init(owner, market); + const [token0, token1] = await Promise.all([ createTokenMint(connection, owner, mintAuthority.publicKey, 6), createTokenMint(connection, owner, mintAuthority.publicKey, 6), @@ -66,7 +66,6 @@ describe("init lp-pool", () => { }, owner ); - const lpPool: LpPoolStructure = await protocol.getLpPool(pair); assert.ok(lpPool); @@ -74,14 +73,13 @@ describe("init lp-pool", () => { assert.ok(lpPool.tokenY.equals(pair.tokenY)); assert.ok(lpPool.fee.v.eq(pair.feeTier.fee)); assert.equal(lpPool.tickSpacing, pair.feeTier.tickSpacing); - assert.ok(lpPool.invariantPosition.equals(PublicKey.default)); + assert.notOk(lpPool.positionExists); assert.ok(lpPool.leftoverX.eq(new BN(0))); assert.ok(lpPool.leftoverY.eq(new BN(0))); - // test if the new LpToken can be minted const [lpToken] = protocol.getLpTokenAddressAndBump(pair); - const mintAmount = new BN(1000); + // create an account for the user const lpTokenAccount = await getOrCreateAssociatedTokenAccount( connection, owner, @@ -103,25 +101,5 @@ describe("init lp-pool", () => { ); assert.equal(lpTokenAccountInfo.amount, 0n); } - - // test mint entrypoint does not support our new PDA format - // await protocol.mint( - // { - // tokenMint: lpToken, - // to: lpTokenAccount.address, - // amount: mintAmount, - // }, - // owner - // ); - - // { - // const lpTokenAccountInfo = await getAccount( - // connection, - // lpTokenAccount.address, - // undefined, - // TOKEN_2022_PROGRAM_ID - // ); - // assert.equal(lpTokenAccountInfo.amount, mintAmount); - // } }); }); diff --git a/protocol/tests/init.test.ts b/protocol/tests/init.test.ts index 1d05d82..16dd7f2 100644 --- a/protocol/tests/init.test.ts +++ b/protocol/tests/init.test.ts @@ -2,7 +2,8 @@ import { AnchorProvider } from "@coral-xyz/anchor"; import { Network } from "../sdk/src/network"; import { Protocol } from "../sdk/src/protocol"; import { Keypair } from "@solana/web3.js"; -import { requestAirdrop } from "./test-utils"; +import { INVARIANT_ADDRESS, requestAirdrop } from "./test-utils"; +import { Market } from "@invariant-labs/sdk-eclipse"; describe("init", () => { const { wallet: walletAnchor, connection } = AnchorProvider.local(); @@ -23,6 +24,13 @@ describe("init", () => { connection ); - await protocol.init(owner); + const market = await Market.build( + Network.LOCAL, + walletAnchor, + connection, + INVARIANT_ADDRESS + ); + + await protocol.init(owner, market); }); }); diff --git a/protocol/tests/math.test.ts b/protocol/tests/math.test.ts index 2c07c2e..54cc2f1 100644 --- a/protocol/tests/math.test.ts +++ b/protocol/tests/math.test.ts @@ -87,7 +87,8 @@ describe("math", () => { const result = lpTokenAmountToLiquidity( { v: new BN(101) }, { v: new BN(1010) }, - { v: new BN(1) } + { v: new BN(1) }, + false ); assert.ok(result.v.eq(new BN(10n))); diff --git a/protocol/tests/mint-high-tick-big.test.ts b/protocol/tests/mint-high-tick-big.test.ts new file mode 100644 index 0000000..b48305a --- /dev/null +++ b/protocol/tests/mint-high-tick-big.test.ts @@ -0,0 +1,250 @@ +import { AnchorProvider, BN } from "@coral-xyz/anchor"; +import { Network } from "../sdk/src/network"; +import { Protocol } from "../sdk/src/protocol"; +import { Keypair, PublicKey } from "@solana/web3.js"; +import { + createTokenMint, + initMarket, + INVARIANT_ADDRESS, + requestAirdrop, +} from "./test-utils"; +import { assert } from "chai"; +import { + getAccount, + getAssociatedTokenAddressSync, + getOrCreateAssociatedTokenAccount, + mintTo, + TOKEN_2022_PROGRAM_ID, +} from "@solana/spl-token"; +import { Pair } from "@invariant-labs/sdk-eclipse"; +import { + fromFee, + getMaxTick, + getMinTick, +} from "@invariant-labs/sdk-eclipse/lib/utils"; +import { + CreateTick, + FeeTier, + Market, +} from "@invariant-labs/sdk-eclipse/lib/market"; +import { lpTokenAmountToLiquidity, ONE_LP_TOKEN } from "../sdk/src"; + +describe("mint lp token", () => { + const { wallet: walletAnchor, connection } = AnchorProvider.local(); + const owner = Keypair.generate(); + const wallet = Keypair.generate(); + const mintAuthority = Keypair.generate(); + + let protocol: Protocol; + let market: Market; + const feeTier: FeeTier = { + fee: fromFee(new BN(600)), + tickSpacing: 5, + }; + let pair: Pair; + let userTokenXAccountAddress: PublicKey; + let userTokenYAccountAddress: PublicKey; + let positionAddress: PublicKey; + + const lowerTick = getMinTick(feeTier.tickSpacing ? feeTier.tickSpacing : 0); + const upperTick = getMaxTick(feeTier.tickSpacing ? feeTier.tickSpacing : 0); + const initTick = 120000; + + const positionId = 0; + + before(async () => { + let giveSOL = [owner.publicKey, mintAuthority.publicKey, wallet.publicKey]; + await Promise.all( + giveSOL.map((account) => requestAirdrop(connection, account, 1e14)) + ); + + market = await Market.build( + Network.LOCAL, + walletAnchor, + connection, + INVARIANT_ADDRESS + ); + + const [token0, token1] = await Promise.all([ + createTokenMint(connection, owner, mintAuthority.publicKey, 6), + createTokenMint(connection, owner, mintAuthority.publicKey, 6), + ]); + pair = new Pair(token0, token1, feeTier); + }); + describe("with initTick = 120000", async () => { + before(async () => { + await initMarket(market, [pair], owner, initTick); + + protocol = await Protocol.build(Network.LOCAL, walletAnchor, connection); + await protocol.init(owner, market); + + const lowerTickVars: CreateTick = { + pair, + index: lowerTick, + payer: owner.publicKey, + }; + const upperTickVars: CreateTick = { + pair, + index: upperTick, + payer: owner.publicKey, + }; + await market.createTick(lowerTickVars, owner); + await market.createTick(upperTickVars, owner); + + const xOwnerAmount = 1e15; + const yOwnerAmount = 1e15; + + const userTokenXAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenX, + owner.publicKey + ); + const userTokenYAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenY, + owner.publicKey + ); + + await mintTo( + connection, + owner, + pair.tokenX, + userTokenXAccount.address, + mintAuthority, + xOwnerAmount + ); + await mintTo( + connection, + owner, + pair.tokenY, + userTokenYAccount.address, + mintAuthority, + yOwnerAmount + ); + userTokenXAccountAddress = getAssociatedTokenAddressSync( + pair.tokenX, + owner.publicKey + ); + userTokenYAccountAddress = getAssociatedTokenAddressSync( + pair.tokenY, + owner.publicKey + ); + + positionAddress = ( + await market.getPositionAddress(protocol.programAuthority, positionId) + ).positionAddress; + }); + + it("big init mint", async () => { + await protocol.initLpPool( + { + pair, + }, + owner + ); + + const [tokenLp] = protocol.getLpTokenAddressAndBump(pair); + const accountLpAddress = getAssociatedTokenAddressSync( + tokenLp, + owner.publicKey, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + const getlpTokenAccount = async () => { + return getAccount( + connection, + accountLpAddress, + undefined, + TOKEN_2022_PROGRAM_ID + ); + }; + + await protocol.mintLpToken( + { + pair, + invariant: market, + liquidityDelta: new BN(ONE_LP_TOKEN * 200000000), + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + + let accountLp = await getlpTokenAccount(); + let lpPool = await protocol.getLpPool(pair); + assert.equal(accountLp.amount, 200000000n); + assert.equal(lpPool.leftoverX, 0); + assert.equal(lpPool.leftoverY, 0); + + let failed = false; + try { + await protocol.mintLpToken( + { + pair, + invariant: market, + + liquidityDelta: new BN(ONE_LP_TOKEN - 1), + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + } catch (e) { + failed = true; + } + + assert(failed, "Mint below expected liquidity amount worked"); + + await protocol.mintLpToken( + { + pair, + invariant: market, + liquidityDelta: new BN(ONE_LP_TOKEN), + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + lpPool = await protocol.getLpPool(pair); + accountLp = await getlpTokenAccount(); + const position = await market.getPosition( + protocol.programAuthority, + positionId + ); + assert.equal(accountLp.amount, 200000001n); + assert.equal(lpPool.leftoverX, 0); + assert.equal(lpPool.leftoverY, 0); + + let deltaLiquidity = lpTokenAmountToLiquidity( + { v: new BN(accountLp.amount) }, + position.liquidity, + { v: new BN(1) }, + true + ); + + await protocol.mintLpToken( + { + pair, + invariant: market, + liquidityDelta: deltaLiquidity.v, + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + + lpPool = await protocol.getLpPool(pair); + accountLp = await getlpTokenAccount(); + assert.equal(accountLp.amount, 200000002n); + assert.equal(lpPool.leftoverX, 0); + assert.equal(lpPool.leftoverY, 0); + }); + }); +}); diff --git a/protocol/tests/mint-high-tick-small.test.ts b/protocol/tests/mint-high-tick-small.test.ts new file mode 100644 index 0000000..b7d3204 --- /dev/null +++ b/protocol/tests/mint-high-tick-small.test.ts @@ -0,0 +1,250 @@ +import { AnchorProvider, BN } from "@coral-xyz/anchor"; +import { Network } from "../sdk/src/network"; +import { Protocol } from "../sdk/src/protocol"; +import { Keypair, PublicKey } from "@solana/web3.js"; +import { + createTokenMint, + initMarket, + INVARIANT_ADDRESS, + requestAirdrop, +} from "./test-utils"; +import { assert } from "chai"; +import { + getAccount, + getAssociatedTokenAddressSync, + getOrCreateAssociatedTokenAccount, + mintTo, + TOKEN_2022_PROGRAM_ID, +} from "@solana/spl-token"; +import { Pair } from "@invariant-labs/sdk-eclipse"; +import { + fromFee, + getMaxTick, + getMinTick, +} from "@invariant-labs/sdk-eclipse/lib/utils"; +import { + CreateTick, + FeeTier, + Market, +} from "@invariant-labs/sdk-eclipse/lib/market"; +import { lpTokenAmountToLiquidity, ONE_LP_TOKEN } from "../sdk/src"; + +describe("mint lp token", () => { + const { wallet: walletAnchor, connection } = AnchorProvider.local(); + const owner = Keypair.generate(); + const wallet = Keypair.generate(); + const mintAuthority = Keypair.generate(); + + let protocol: Protocol; + let market: Market; + const feeTier: FeeTier = { + fee: fromFee(new BN(600)), + tickSpacing: 5, + }; + let pair: Pair; + let userTokenXAccountAddress: PublicKey; + let userTokenYAccountAddress: PublicKey; + let positionAddress: PublicKey; + + const lowerTick = getMinTick(feeTier.tickSpacing ? feeTier.tickSpacing : 0); + const upperTick = getMaxTick(feeTier.tickSpacing ? feeTier.tickSpacing : 0); + const initTick = 120000; + + const positionId = 0; + + before(async () => { + let giveSOL = [owner.publicKey, mintAuthority.publicKey, wallet.publicKey]; + await Promise.all( + giveSOL.map((account) => requestAirdrop(connection, account, 1e14)) + ); + + market = await Market.build( + Network.LOCAL, + walletAnchor, + connection, + INVARIANT_ADDRESS + ); + + const [token0, token1] = await Promise.all([ + createTokenMint(connection, owner, mintAuthority.publicKey, 6), + createTokenMint(connection, owner, mintAuthority.publicKey, 6), + ]); + pair = new Pair(token0, token1, feeTier); + }); + describe("with initTick = 120000", async () => { + before(async () => { + await initMarket(market, [pair], owner, initTick); + + protocol = await Protocol.build(Network.LOCAL, walletAnchor, connection); + await protocol.init(owner, market); + + const lowerTickVars: CreateTick = { + pair, + index: lowerTick, + payer: owner.publicKey, + }; + const upperTickVars: CreateTick = { + pair, + index: upperTick, + payer: owner.publicKey, + }; + await market.createTick(lowerTickVars, owner); + await market.createTick(upperTickVars, owner); + + const xOwnerAmount = 1e15; + const yOwnerAmount = 1e15; + + const userTokenXAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenX, + owner.publicKey + ); + const userTokenYAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenY, + owner.publicKey + ); + + await mintTo( + connection, + owner, + pair.tokenX, + userTokenXAccount.address, + mintAuthority, + xOwnerAmount + ); + await mintTo( + connection, + owner, + pair.tokenY, + userTokenYAccount.address, + mintAuthority, + yOwnerAmount + ); + userTokenXAccountAddress = getAssociatedTokenAddressSync( + pair.tokenX, + owner.publicKey + ); + userTokenYAccountAddress = getAssociatedTokenAddressSync( + pair.tokenY, + owner.publicKey + ); + + positionAddress = ( + await market.getPositionAddress(protocol.programAuthority, positionId) + ).positionAddress; + }); + + it("small init mint", async () => { + await protocol.initLpPool( + { + pair, + }, + owner + ); + + const [tokenLp] = protocol.getLpTokenAddressAndBump(pair); + const accountLpAddress = getAssociatedTokenAddressSync( + tokenLp, + owner.publicKey, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + const getlpTokenAccount = async () => { + return getAccount( + connection, + accountLpAddress, + undefined, + TOKEN_2022_PROGRAM_ID + ); + }; + + let failed = false; + try { + await protocol.mintLpToken( + { + pair, + invariant: market, + + liquidityDelta: new BN(ONE_LP_TOKEN - 1), + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + } catch (e) { + failed = true; + } + + assert(failed, "Mint below expected liquidity amount worked"); + + await protocol.mintLpToken( + { + pair, + invariant: market, + liquidityDelta: new BN(ONE_LP_TOKEN), + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + + let accountLp = await getlpTokenAccount(); + let lpPool = await protocol.getLpPool(pair); + assert.equal(accountLp.amount, 1n); + assert.equal(lpPool.leftoverX, 0); + assert.equal(lpPool.leftoverY, 0); + + await protocol.mintLpToken( + { + pair, + invariant: market, + liquidityDelta: new BN(ONE_LP_TOKEN * 200000000), + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + lpPool = await protocol.getLpPool(pair); + accountLp = await getlpTokenAccount(); + const position = await market.getPosition( + protocol.programAuthority, + positionId + ); + assert.equal(accountLp.amount, 200000001n); + assert.equal(lpPool.leftoverX, 0); + assert.equal(lpPool.leftoverY, 0); + + let deltaLiquidity = lpTokenAmountToLiquidity( + { v: new BN(accountLp.amount) }, + position.liquidity, + { v: new BN(1) }, + true + ); + + await protocol.mintLpToken( + { + pair, + invariant: market, + liquidityDelta: deltaLiquidity.v, + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + + lpPool = await protocol.getLpPool(pair); + accountLp = await getlpTokenAccount(); + assert.equal(accountLp.amount, 200000002n); + assert.equal(lpPool.leftoverX, 0); + assert.equal(lpPool.leftoverY, 0); + }); + }); +}); diff --git a/protocol/tests/mint-zero-tick-big.test.ts b/protocol/tests/mint-zero-tick-big.test.ts new file mode 100644 index 0000000..f15c854 --- /dev/null +++ b/protocol/tests/mint-zero-tick-big.test.ts @@ -0,0 +1,250 @@ +import { AnchorProvider, BN } from "@coral-xyz/anchor"; +import { Network } from "../sdk/src/network"; +import { Protocol } from "../sdk/src/protocol"; +import { Keypair, PublicKey } from "@solana/web3.js"; +import { + createTokenMint, + initMarket, + INVARIANT_ADDRESS, + requestAirdrop, +} from "./test-utils"; +import { assert } from "chai"; +import { + getAccount, + getAssociatedTokenAddressSync, + getOrCreateAssociatedTokenAccount, + mintTo, + TOKEN_2022_PROGRAM_ID, +} from "@solana/spl-token"; +import { Pair } from "@invariant-labs/sdk-eclipse"; +import { + fromFee, + getMaxTick, + getMinTick, +} from "@invariant-labs/sdk-eclipse/lib/utils"; +import { + CreateTick, + FeeTier, + Market, +} from "@invariant-labs/sdk-eclipse/lib/market"; +import { lpTokenAmountToLiquidity, ONE_LP_TOKEN } from "../sdk/src"; + +describe("mint lp token", () => { + const { wallet: walletAnchor, connection } = AnchorProvider.local(); + const owner = Keypair.generate(); + const wallet = Keypair.generate(); + const mintAuthority = Keypair.generate(); + + let protocol: Protocol; + let market: Market; + const feeTier: FeeTier = { + fee: fromFee(new BN(600)), + tickSpacing: 5, + }; + let pair: Pair; + let userTokenXAccountAddress: PublicKey; + let userTokenYAccountAddress: PublicKey; + let positionAddress: PublicKey; + + const lowerTick = getMinTick(feeTier.tickSpacing ? feeTier.tickSpacing : 0); + const upperTick = getMaxTick(feeTier.tickSpacing ? feeTier.tickSpacing : 0); + const initTick = 0; + + const positionId = 0; + + before(async () => { + let giveSOL = [owner.publicKey, mintAuthority.publicKey, wallet.publicKey]; + await Promise.all( + giveSOL.map((account) => requestAirdrop(connection, account, 1e14)) + ); + + market = await Market.build( + Network.LOCAL, + walletAnchor, + connection, + INVARIANT_ADDRESS + ); + + const [token0, token1] = await Promise.all([ + createTokenMint(connection, owner, mintAuthority.publicKey, 6), + createTokenMint(connection, owner, mintAuthority.publicKey, 6), + ]); + pair = new Pair(token0, token1, feeTier); + }); + describe("with initTick = 0", async () => { + before(async () => { + await initMarket(market, [pair], owner, initTick); + + protocol = await Protocol.build(Network.LOCAL, walletAnchor, connection); + await protocol.init(owner, market); + + const lowerTickVars: CreateTick = { + pair, + index: lowerTick, + payer: owner.publicKey, + }; + const upperTickVars: CreateTick = { + pair, + index: upperTick, + payer: owner.publicKey, + }; + await market.createTick(lowerTickVars, owner); + await market.createTick(upperTickVars, owner); + + const xOwnerAmount = 1e15; + const yOwnerAmount = 1e15; + + const userTokenXAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenX, + owner.publicKey + ); + const userTokenYAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenY, + owner.publicKey + ); + + await mintTo( + connection, + owner, + pair.tokenX, + userTokenXAccount.address, + mintAuthority, + xOwnerAmount + ); + await mintTo( + connection, + owner, + pair.tokenY, + userTokenYAccount.address, + mintAuthority, + yOwnerAmount + ); + userTokenXAccountAddress = getAssociatedTokenAddressSync( + pair.tokenX, + owner.publicKey + ); + userTokenYAccountAddress = getAssociatedTokenAddressSync( + pair.tokenY, + owner.publicKey + ); + + positionAddress = ( + await market.getPositionAddress(protocol.programAuthority, positionId) + ).positionAddress; + }); + + it("big init mint", async () => { + await protocol.initLpPool( + { + pair, + }, + owner + ); + + const [tokenLp] = protocol.getLpTokenAddressAndBump(pair); + const accountLpAddress = getAssociatedTokenAddressSync( + tokenLp, + owner.publicKey, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + const getlpTokenAccount = async () => { + return getAccount( + connection, + accountLpAddress, + undefined, + TOKEN_2022_PROGRAM_ID + ); + }; + + await protocol.mintLpToken( + { + pair, + invariant: market, + liquidityDelta: new BN(ONE_LP_TOKEN * 200000000), + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + + let accountLp = await getlpTokenAccount(); + let lpPool = await protocol.getLpPool(pair); + assert.equal(accountLp.amount, 200000000n); + assert.equal(lpPool.leftoverX, 0); + assert.equal(lpPool.leftoverY, 0); + + let failed = false; + try { + await protocol.mintLpToken( + { + pair, + invariant: market, + + liquidityDelta: new BN(ONE_LP_TOKEN - 1), + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + } catch (e) { + failed = true; + } + + assert(failed, "Mint below expected liquidity amount worked"); + + await protocol.mintLpToken( + { + pair, + invariant: market, + liquidityDelta: new BN(ONE_LP_TOKEN), + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + lpPool = await protocol.getLpPool(pair); + accountLp = await getlpTokenAccount(); + const position = await market.getPosition( + protocol.programAuthority, + positionId + ); + assert.equal(accountLp.amount, 200000001n); + assert.equal(lpPool.leftoverX, 0); + assert.equal(lpPool.leftoverY, 0); + + let deltaLiquidity = lpTokenAmountToLiquidity( + { v: new BN(accountLp.amount) }, + position.liquidity, + { v: new BN(1) }, + true + ); + + await protocol.mintLpToken( + { + pair, + invariant: market, + liquidityDelta: deltaLiquidity.v, + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + + lpPool = await protocol.getLpPool(pair); + accountLp = await getlpTokenAccount(); + assert.equal(accountLp.amount, 200000002n); + assert.equal(lpPool.leftoverX, 0); + assert.equal(lpPool.leftoverY, 0); + }); + }); +}); diff --git a/protocol/tests/mint-zero-tick-small.test.ts b/protocol/tests/mint-zero-tick-small.test.ts new file mode 100644 index 0000000..4ae39dc --- /dev/null +++ b/protocol/tests/mint-zero-tick-small.test.ts @@ -0,0 +1,250 @@ +import { AnchorProvider, BN } from "@coral-xyz/anchor"; +import { Network } from "../sdk/src/network"; +import { Protocol } from "../sdk/src/protocol"; +import { Keypair, PublicKey } from "@solana/web3.js"; +import { + createTokenMint, + initMarket, + INVARIANT_ADDRESS, + requestAirdrop, +} from "./test-utils"; +import { assert } from "chai"; +import { + getAccount, + getAssociatedTokenAddressSync, + getOrCreateAssociatedTokenAccount, + mintTo, + TOKEN_2022_PROGRAM_ID, +} from "@solana/spl-token"; +import { Pair } from "@invariant-labs/sdk-eclipse"; +import { + fromFee, + getMaxTick, + getMinTick, +} from "@invariant-labs/sdk-eclipse/lib/utils"; +import { + CreateTick, + FeeTier, + Market, +} from "@invariant-labs/sdk-eclipse/lib/market"; +import { lpTokenAmountToLiquidity, ONE_LP_TOKEN } from "../sdk/src"; + +describe("mint lp token", () => { + const { wallet: walletAnchor, connection } = AnchorProvider.local(); + const owner = Keypair.generate(); + const wallet = Keypair.generate(); + const mintAuthority = Keypair.generate(); + + let protocol: Protocol; + let market: Market; + const feeTier: FeeTier = { + fee: fromFee(new BN(600)), + tickSpacing: 5, + }; + let pair: Pair; + let userTokenXAccountAddress: PublicKey; + let userTokenYAccountAddress: PublicKey; + let positionAddress: PublicKey; + + const lowerTick = getMinTick(feeTier.tickSpacing ? feeTier.tickSpacing : 0); + const upperTick = getMaxTick(feeTier.tickSpacing ? feeTier.tickSpacing : 0); + const initTick = 0; + + const positionId = 0; + + before(async () => { + let giveSOL = [owner.publicKey, mintAuthority.publicKey, wallet.publicKey]; + await Promise.all( + giveSOL.map((account) => requestAirdrop(connection, account, 1e14)) + ); + + market = await Market.build( + Network.LOCAL, + walletAnchor, + connection, + INVARIANT_ADDRESS + ); + + const [token0, token1] = await Promise.all([ + createTokenMint(connection, owner, mintAuthority.publicKey, 6), + createTokenMint(connection, owner, mintAuthority.publicKey, 6), + ]); + pair = new Pair(token0, token1, feeTier); + }); + describe("with initTick = 0", async () => { + before(async () => { + await initMarket(market, [pair], owner, initTick); + + protocol = await Protocol.build(Network.LOCAL, walletAnchor, connection); + await protocol.init(owner, market); + + const lowerTickVars: CreateTick = { + pair, + index: lowerTick, + payer: owner.publicKey, + }; + const upperTickVars: CreateTick = { + pair, + index: upperTick, + payer: owner.publicKey, + }; + await market.createTick(lowerTickVars, owner); + await market.createTick(upperTickVars, owner); + + const xOwnerAmount = 1e15; + const yOwnerAmount = 1e15; + + const userTokenXAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenX, + owner.publicKey + ); + const userTokenYAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenY, + owner.publicKey + ); + + await mintTo( + connection, + owner, + pair.tokenX, + userTokenXAccount.address, + mintAuthority, + xOwnerAmount + ); + await mintTo( + connection, + owner, + pair.tokenY, + userTokenYAccount.address, + mintAuthority, + yOwnerAmount + ); + userTokenXAccountAddress = getAssociatedTokenAddressSync( + pair.tokenX, + owner.publicKey + ); + userTokenYAccountAddress = getAssociatedTokenAddressSync( + pair.tokenY, + owner.publicKey + ); + + positionAddress = ( + await market.getPositionAddress(protocol.programAuthority, positionId) + ).positionAddress; + }); + + it("small init mint", async () => { + await protocol.initLpPool( + { + pair, + }, + owner + ); + + const [tokenLp] = protocol.getLpTokenAddressAndBump(pair); + const accountLpAddress = getAssociatedTokenAddressSync( + tokenLp, + owner.publicKey, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + const getlpTokenAccount = async () => { + return getAccount( + connection, + accountLpAddress, + undefined, + TOKEN_2022_PROGRAM_ID + ); + }; + + let failed = false; + try { + await protocol.mintLpToken( + { + pair, + invariant: market, + + liquidityDelta: new BN(ONE_LP_TOKEN - 1), + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + } catch (e) { + failed = true; + } + + assert(failed, "Mint below expected liquidity amount worked"); + + await protocol.mintLpToken( + { + pair, + invariant: market, + liquidityDelta: new BN(ONE_LP_TOKEN), + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + + let accountLp = await getlpTokenAccount(); + let lpPool = await protocol.getLpPool(pair); + assert.equal(accountLp.amount, 1n); + assert.equal(lpPool.leftoverX, 0); + assert.equal(lpPool.leftoverY, 0); + + await protocol.mintLpToken( + { + pair, + invariant: market, + liquidityDelta: new BN(ONE_LP_TOKEN * 200000000), + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + lpPool = await protocol.getLpPool(pair); + accountLp = await getlpTokenAccount(); + const position = await market.getPosition( + protocol.programAuthority, + positionId + ); + assert.equal(accountLp.amount, 200000001n); + assert.equal(lpPool.leftoverX, 0); + assert.equal(lpPool.leftoverY, 0); + + let deltaLiquidity = lpTokenAmountToLiquidity( + { v: new BN(accountLp.amount) }, + position.liquidity, + { v: new BN(1) }, + true + ); + + await protocol.mintLpToken( + { + pair, + invariant: market, + liquidityDelta: deltaLiquidity.v, + position: positionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + + lpPool = await protocol.getLpPool(pair); + accountLp = await getlpTokenAccount(); + assert.equal(accountLp.amount, 200000002n); + assert.equal(lpPool.leftoverX, 0); + assert.equal(lpPool.leftoverY, 0); + }); + }); +}); diff --git a/protocol/tests/mint.test.ts b/protocol/tests/mint.test.ts deleted file mode 100644 index d8cc6d5..0000000 --- a/protocol/tests/mint.test.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { AnchorProvider, BN } from "@coral-xyz/anchor"; -import { Network } from "../sdk/src/network"; -import { Protocol } from "../sdk/src/protocol"; -import { Keypair } from "@solana/web3.js"; -import { - createTokenMint, - initMarket, - INVARIANT_ADDRESS, - requestAirdrop, -} from "./test-utils"; -import { assert } from "chai"; -import { - ASSOCIATED_TOKEN_PROGRAM_ID, - createAssociatedTokenAccount, - getOrCreateAssociatedTokenAccount, - mintTo, - TOKEN_2022_PROGRAM_ID, -} from "@solana/spl-token"; -import { Pair } from "@invariant-labs/sdk-eclipse"; -import { - fromFee, - getMaxTick, - getMinTick, -} from "@invariant-labs/sdk-eclipse/lib/utils"; -import { - CreateTick, - FeeTier, - Market, -} from "@invariant-labs/sdk-eclipse/lib/market"; - -describe("mint lp token", () => { - const { wallet: walletAnchor, connection } = AnchorProvider.local(); - const owner = Keypair.generate(); - const wallet = Keypair.generate(); - const mintAuthority = Keypair.generate(); - - let protocol: Protocol; - let market: Market; - const feeTier: FeeTier = { - fee: fromFee(new BN(600)), - tickSpacing: 10, - }; - let pair: Pair; - const lowerTick = getMinTick(feeTier.tickSpacing ? feeTier.tickSpacing : 0); - const upperTick = getMaxTick(feeTier.tickSpacing ? feeTier.tickSpacing : 0); - const initTick = 0; - - before(async () => { - let giveSOL = [owner.publicKey, mintAuthority.publicKey, wallet.publicKey]; - await Promise.all( - giveSOL.map((account) => requestAirdrop(connection, account, 1e14)) - ); - - market = await Market.build( - Network.LOCAL, - walletAnchor, - connection, - INVARIANT_ADDRESS - ); - - const [token0, token1] = await Promise.all([ - createTokenMint(connection, owner, mintAuthority.publicKey, 6), - createTokenMint(connection, owner, mintAuthority.publicKey, 6), - ]); - pair = new Pair(token0, token1, feeTier); - - await initMarket(market, [pair], owner, initTick); - - protocol = await Protocol.build(Network.LOCAL, walletAnchor, connection); - await protocol.initWithPositionlist(owner, market); - - const lowerTickVars: CreateTick = { - pair, - index: lowerTick, - payer: owner.publicKey, - }; - const upperTickVars: CreateTick = { - pair, - index: upperTick, - payer: owner.publicKey, - }; - await market.createTick(lowerTickVars, owner); - await market.createTick(upperTickVars, owner); - - const xOwnerAmount = 1e10; - const yOwnerAmount = 1e10; - - const userTokenXAccount = await getOrCreateAssociatedTokenAccount( - connection, - owner, - pair.tokenX, - owner.publicKey - ); - const userTokenYAccount = await getOrCreateAssociatedTokenAccount( - connection, - owner, - pair.tokenY, - owner.publicKey - ); - - await mintTo( - connection, - owner, - pair.tokenX, - userTokenXAccount.address, - mintAuthority, - xOwnerAmount - ); - await mintTo( - connection, - owner, - pair.tokenY, - userTokenYAccount.address, - mintAuthority, - yOwnerAmount - ); - }); - - it("test", async () => { - const userTokenXAccount = await getOrCreateAssociatedTokenAccount( - connection, - owner, - pair.tokenX, - owner.publicKey - ); - const userTokenYAccount = await getOrCreateAssociatedTokenAccount( - connection, - owner, - pair.tokenY, - owner.publicKey - ); - - // TODO: make it support cases where the id is different from 0 -> multiple positions for user aka multiple LP pools - const positionId = 0; - const lastPositionId = 0; - - const liquidityDelta = new BN(500000000); - - const { address: stateAddress } = await market.getStateAddress(); - const poolAddress = await pair.getAddress(INVARIANT_ADDRESS); - const { positionListAddress: positionListAddress } = - await market.getPositionListAddress(protocol.programAuthority); - const { positionAddress } = await market.getPositionAddress( - protocol.programAuthority, - positionId - ); - const { positionAddress: lastPositionAddress } = - await market.getPositionAddress( - protocol.programAuthority, - lastPositionId - ); - const { tickAddress: lowerTickAddress } = await market.getTickAddress( - pair, - lowerTick - ); - const { tickAddress: upperTickAddress } = await market.getTickAddress( - pair, - upperTick - ); - const { tokenXReserve, tokenYReserve, tickmap } = await market.getPool( - pair - ); - - await protocol.initLpPool( - { - pair, - }, - owner - ); - - const [tokenLp] = protocol.getLpTokenAddressAndBump(pair); - // getorCreate has the advantage since you can call it several times with no downsides - const accountLp = await createAssociatedTokenAccount( - connection, - owner, - tokenLp, - owner.publicKey, - undefined, - TOKEN_2022_PROGRAM_ID, - ASSOCIATED_TOKEN_PROGRAM_ID - ); - - await protocol.mintLpToken( - { - liquidityDelta, - pair, - index: positionId, - invProgram: INVARIANT_ADDRESS, - invState: stateAddress, - position: positionAddress, - lastPosition: lastPositionAddress, - pool: poolAddress, - positionList: positionListAddress, - lowerTick: lowerTickAddress, - upperTick: upperTickAddress, - tickmap, - accountX: userTokenXAccount.address, - accountY: userTokenYAccount.address, - invReserveX: tokenXReserve, - invReserveY: tokenYReserve, - invProgramAuthority: market.programAuthority, - }, - owner - ); - - console.log("mint 2"); - - await protocol.mintLpToken( - { - liquidityDelta, - pair, - index: positionId, - invProgram: INVARIANT_ADDRESS, - invState: stateAddress, - position: positionAddress, - lastPosition: lastPositionAddress, - pool: poolAddress, - positionList: positionListAddress, - lowerTick: lowerTickAddress, - upperTick: upperTickAddress, - tickmap, - accountX: userTokenXAccount.address, - accountY: userTokenYAccount.address, - invReserveX: tokenXReserve, - invReserveY: tokenYReserve, - invProgramAuthority: market.programAuthority, - }, - owner - ); - - console.log("mint 3"); - - await protocol.mintLpToken( - { - liquidityDelta, - pair, - index: positionId, - invProgram: INVARIANT_ADDRESS, - invState: stateAddress, - position: positionAddress, - lastPosition: lastPositionAddress, - pool: poolAddress, - positionList: positionListAddress, - lowerTick: lowerTickAddress, - upperTick: upperTickAddress, - tickmap, - accountX: userTokenXAccount.address, - accountY: userTokenYAccount.address, - invReserveX: tokenXReserve, - invReserveY: tokenYReserve, - invProgramAuthority: market.programAuthority, - }, - owner - ); - - console.log("mint 4"); - - await protocol.mintLpToken( - { - liquidityDelta, - pair, - index: positionId, - invProgram: INVARIANT_ADDRESS, - invState: stateAddress, - position: positionAddress, - lastPosition: lastPositionAddress, - pool: poolAddress, - positionList: positionListAddress, - lowerTick: lowerTickAddress, - upperTick: upperTickAddress, - tickmap, - accountX: userTokenXAccount.address, - accountY: userTokenYAccount.address, - invReserveX: tokenXReserve, - invReserveY: tokenYReserve, - invProgramAuthority: market.programAuthority, - }, - owner - ); - - const position = await market.getPosition( - protocol.programAuthority, - positionId - ); - console.log(position); - assert.ok(position); - }); -}); diff --git a/protocol/tests/multi-pool.test.ts b/protocol/tests/multi-pool.test.ts new file mode 100644 index 0000000..de2bb75 --- /dev/null +++ b/protocol/tests/multi-pool.test.ts @@ -0,0 +1,758 @@ +import { AnchorProvider, BN } from "@coral-xyz/anchor"; +import { Network } from "../sdk/src/network"; +import { Protocol } from "../sdk/src/protocol"; +import { Keypair } from "@solana/web3.js"; +import { + createTokenMint, + initMarket, + INVARIANT_ADDRESS, + requestAirdrop, +} from "./test-utils"; +import { assert } from "chai"; +import { + getAccount, + getAssociatedTokenAddressSync, + getOrCreateAssociatedTokenAccount, + mintTo, + TOKEN_2022_PROGRAM_ID, +} from "@solana/spl-token"; +import { Pair } from "@invariant-labs/sdk-eclipse"; +import { + fromFee, + getMaxTick, + getMinTick, +} from "@invariant-labs/sdk-eclipse/lib/utils"; +import { + CreateTick, + FeeTier, + Market, +} from "@invariant-labs/sdk-eclipse/lib/market"; +import { lpTokenAmountToLiquidity } from "../sdk/src"; + +describe("multiple pools", () => { + const { wallet: walletAnchor, connection } = AnchorProvider.local(); + const owner = Keypair.generate(); + const wallet = Keypair.generate(); + const mintAuthority = Keypair.generate(); + + let protocol: Protocol; + let market: Market; + const feeTier: FeeTier = { + fee: fromFee(new BN(600)), + tickSpacing: 10, + }; + + let pair01: Pair; + let pair02: Pair; + let pair12: Pair; + const lowerTick = getMinTick(feeTier.tickSpacing ?? 0); + const upperTick = getMaxTick(feeTier.tickSpacing ?? 0); + const initTick = 0; + + before(async () => { + let giveSOL = [owner.publicKey, mintAuthority.publicKey, wallet.publicKey]; + await Promise.all( + giveSOL.map((account) => requestAirdrop(connection, account, 1e14)) + ); + + market = await Market.build( + Network.LOCAL, + walletAnchor, + connection, + INVARIANT_ADDRESS + ); + + const [token0, token1, token2] = await Promise.all([ + createTokenMint(connection, owner, mintAuthority.publicKey, 6), + createTokenMint(connection, owner, mintAuthority.publicKey, 6), + createTokenMint(connection, owner, mintAuthority.publicKey, 6), + ]); + pair01 = new Pair(token0, token1, feeTier); + pair02 = new Pair(token0, token2, feeTier); + pair12 = new Pair(token1, token2, feeTier); + let pairs = [pair01, pair02, pair12]; + + await initMarket(market, pairs, owner, initTick); + + protocol = await Protocol.build(Network.LOCAL, walletAnchor, connection); + await protocol.init(owner, market); + + for (const pair of pairs) { + const lowerTickVars: CreateTick = { + pair, + index: lowerTick, + payer: owner.publicKey, + }; + const upperTickVars: CreateTick = { + pair, + index: upperTick, + payer: owner.publicKey, + }; + await market.createTick(lowerTickVars, owner); + await market.createTick(upperTickVars, owner); + } + + const ownerAmount = 1e10; + + const userTokenXAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + token0, + owner.publicKey + ); + const userTokenYAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + token1, + owner.publicKey + ); + const userTokenZAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + token2, + owner.publicKey + ); + + await mintTo( + connection, + owner, + token0, + userTokenXAccount.address, + mintAuthority, + ownerAmount + ); + await mintTo( + connection, + owner, + token1, + userTokenYAccount.address, + mintAuthority, + ownerAmount + ); + await mintTo( + connection, + owner, + token2, + userTokenZAccount.address, + mintAuthority, + ownerAmount + ); + }); + + it("mint multiple [pair 01]:[position 0]", async () => { + let pair = pair01; + const userTokenXAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenX, + owner.publicKey + ); + const userTokenYAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenY, + owner.publicKey + ); + + const positionCount = ( + await market.getPositionList(protocol.programAuthority) + ).head; + + // if the LPPool is going to be created, the positionId is the next uninitialized position + const newPositionId = positionCount; + + const liquidityDelta = new BN(500000000); + + const { positionAddress } = await market.getPositionAddress( + protocol.programAuthority, + newPositionId + ); + + await protocol.initLpPool( + { + pair, + }, + owner + ); + + await protocol.mintLpToken( + { + liquidityDelta, + invariant: market, + pair, + position: positionAddress, + accountX: userTokenXAccount.address, + accountY: userTokenYAccount.address, + }, + owner + ); + + await protocol.mintLpToken( + { + liquidityDelta, + invariant: market, + pair, + position: positionAddress, + accountX: userTokenXAccount.address, + accountY: userTokenYAccount.address, + }, + owner + ); + + const position = await market.getPosition( + protocol.programAuthority, + newPositionId + ); + assert.ok(position); + }); + + // pair02 + it("mint multiple [pair 02]:[position 1]", async () => { + const pair = pair02; + const userTokenXAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenX, + owner.publicKey + ); + const userTokenYAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenY, + owner.publicKey + ); + + const positionCount = ( + await market.getPositionList(protocol.programAuthority) + ).head; + // if the LPPool is going to be created, the positionId is the next uninitialized position + const positionId = positionCount; + + const liquidityDelta = new BN(500000000); + + const { positionAddress } = await market.getPositionAddress( + protocol.programAuthority, + positionId + ); + + await protocol.initLpPool( + { + pair, + }, + owner + ); + + await protocol.mintLpToken( + { + liquidityDelta, + pair, + invariant: market, + + position: positionAddress, + accountX: userTokenXAccount.address, + accountY: userTokenYAccount.address, + }, + owner + ); + + await protocol.mintLpToken( + { + liquidityDelta, + pair, + invariant: market, + + position: positionAddress, + accountX: userTokenXAccount.address, + accountY: userTokenYAccount.address, + }, + owner + ); + + await protocol.mintLpToken( + { + liquidityDelta, + pair, + invariant: market, + + position: positionAddress, + accountX: userTokenXAccount.address, + accountY: userTokenYAccount.address, + }, + owner + ); + + await protocol.mintLpToken( + { + liquidityDelta, + pair, + invariant: market, + + position: positionAddress, + accountX: userTokenXAccount.address, + accountY: userTokenYAccount.address, + }, + owner + ); + + const position = await market.getPosition( + protocol.programAuthority, + positionId + ); + assert.ok(position); + }); + + it("mint multiple [pair 12]:[position 2]", async () => { + const pair = pair12; + const userTokenXAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenX, + owner.publicKey + ); + const userTokenYAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenY, + owner.publicKey + ); + + const positionCount = ( + await market.getPositionList(protocol.programAuthority) + ).head; + // if the LPPool is going to be created, the positionId is the next uninitialized position + const positionId = positionCount; + + const liquidityDelta = new BN(500000000); + + const { positionAddress } = await market.getPositionAddress( + protocol.programAuthority, + positionId + ); + + await protocol.initLpPool( + { + pair, + }, + owner + ); + + await protocol.mintLpToken( + { + liquidityDelta, + pair, + invariant: market, + + position: positionAddress, + accountX: userTokenXAccount.address, + accountY: userTokenYAccount.address, + }, + owner + ); + + await protocol.mintLpToken( + { + liquidityDelta, + pair, + invariant: market, + + position: positionAddress, + accountX: userTokenXAccount.address, + accountY: userTokenYAccount.address, + }, + owner + ); + + const position = await market.getPosition( + protocol.programAuthority, + positionId + ); + assert.ok(position); + }); + + it("burn all [pair 01]:[position 0]", async () => { + const pair = pair01; + + const userTokenXAccountAddress = getAssociatedTokenAddressSync( + pair.tokenX, + owner.publicKey + ); + const userTokenYAccountAddress = getAssociatedTokenAddressSync( + pair.tokenY, + owner.publicKey + ); + + const positionId = 0; + const { positionAddress } = await market.getPositionAddress( + protocol.programAuthority, + positionId + ); + + const positionCount = ( + await market.getPositionList(protocol.programAuthority) + ).head; + const lastPositionId = positionCount == 0 ? 0 : positionCount - 1; + const lastPosition = await market.getPosition( + protocol.programAuthority, + lastPositionId + ); + const lastPositionPool = await market.getPoolByAddress(lastPosition.pool); + const [lastPositionLpPool] = protocol.getLpPoolAddressAndBump( + new Pair(lastPositionPool.tokenX, lastPositionPool.tokenY, { + fee: new BN(lastPositionPool.fee.v), + tickSpacing: lastPositionPool.tickSpacing, + }) + ); + + const { positionAddress: lastPositionAddress } = + await market.getPositionAddress( + protocol.programAuthority, + lastPositionId + ); + + const positionBeforeLastBurn = await market.getPosition( + protocol.programAuthority, + 0 + ); + await protocol.burnLpToken( + { + pair, + invariant: market, + + liquidityDelta: positionBeforeLastBurn.liquidity.v, + + lastPositionLpPool, + + position: positionAddress, + lastPosition: lastPositionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + + const lpPool = await protocol.getLpPool(pair); + assert.notOk(lpPool.positionExists); + + let err = false; + try { + await market.getPosition(protocol.programAuthority, 2); + } catch (e) { + err = true; + } + assert(err, "burn did not remove position 2"); + + const newFirstPosition = await market.getPosition( + protocol.programAuthority, + 0 + ); + const newFirstPositionPool = await market.getPoolByAddress( + newFirstPosition.pool + ); + const newFirstPositionLpPool = await protocol.getLpPool( + new Pair(newFirstPositionPool.tokenX, newFirstPositionPool.tokenY, { + fee: new BN(newFirstPositionPool.fee.v), + tickSpacing: newFirstPositionPool.tickSpacing, + }) + ); + assert(newFirstPositionLpPool.tokenX.equals(pair12.tokenX)); + assert(newFirstPositionLpPool.tokenY.equals(pair12.tokenY)); + }); + + it("burn all [pair 02]:[position 1]", async () => { + const pair = pair02; + + const userTokenXAccountAddress = getAssociatedTokenAddressSync( + pair.tokenX, + owner.publicKey + ); + const userTokenYAccountAddress = getAssociatedTokenAddressSync( + pair.tokenY, + owner.publicKey + ); + + const positionId = 1; + const { positionAddress } = await market.getPositionAddress( + protocol.programAuthority, + positionId + ); + + const positionCount = ( + await market.getPositionList(protocol.programAuthority) + ).head; + const lastPositionId = positionCount == 0 ? 0 : positionCount - 1; + const lastPosition = await market.getPosition( + protocol.programAuthority, + lastPositionId + ); + const lastPositionPool = await market.getPoolByAddress(lastPosition.pool); + const [lastPositionLpPool] = protocol.getLpPoolAddressAndBump( + new Pair(lastPositionPool.tokenX, lastPositionPool.tokenY, { + fee: new BN(lastPositionPool.fee.v), + tickSpacing: lastPositionPool.tickSpacing, + }) + ); + + const { positionAddress: lastPositionAddress } = + await market.getPositionAddress( + protocol.programAuthority, + lastPositionId + ); + + const positionBeforeLastBurn = await market.getPosition( + protocol.programAuthority, + 1 + ); + await protocol.burnLpToken( + { + pair, + invariant: market, + + liquidityDelta: positionBeforeLastBurn.liquidity.v, + + lastPositionLpPool, + + position: positionAddress, + lastPosition: lastPositionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + + const lpPool = await protocol.getLpPool(pair); + assert.notOk(lpPool.positionExists); + + let err = false; + try { + await market.getPosition(protocol.programAuthority, 1); + } catch (e) { + err = true; + } + assert(err, "burn did not remove position 1"); + + const newFirstPositionLpPool = await protocol.getLpPool(pair); + assert.notOk(newFirstPositionLpPool.positionExists); + }); + + it("mint multiple [pair 01]:[position 1]", async () => { + let pair = pair01; + + const lowerTickVars: CreateTick = { + pair, + index: lowerTick, + payer: owner.publicKey, + }; + const upperTickVars: CreateTick = { + pair, + index: upperTick, + payer: owner.publicKey, + }; + await market.createTick(lowerTickVars, owner); + await market.createTick(upperTickVars, owner); + + const userTokenXAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenX, + owner.publicKey + ); + const userTokenYAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenY, + owner.publicKey + ); + + const positionCount = ( + await market.getPositionList(protocol.programAuthority) + ).head; + + // if the LPPool is going to be created, the positionId is the next uninitialized position + const newPositionId = positionCount; + + const liquidityDelta = new BN(500000000); + + const { positionAddress } = await market.getPositionAddress( + protocol.programAuthority, + newPositionId + ); + + await protocol.mintLpToken( + { + liquidityDelta, + invariant: market, + pair, + position: positionAddress, + accountX: userTokenXAccount.address, + accountY: userTokenYAccount.address, + }, + owner + ); + + await protocol.mintLpToken( + { + liquidityDelta, + invariant: market, + pair, + position: positionAddress, + accountX: userTokenXAccount.address, + accountY: userTokenYAccount.address, + }, + owner + ); + + const position = await market.getPosition( + protocol.programAuthority, + newPositionId + ); + assert.ok(position); + }); + + it("burn half [pair 01]:[position 1]", async () => { + const pair = pair01; + + const userTokenXAccountAddress = getAssociatedTokenAddressSync( + pair.tokenX, + owner.publicKey + ); + const userTokenYAccountAddress = getAssociatedTokenAddressSync( + pair.tokenY, + owner.publicKey + ); + + const positionId = 1; + const { positionAddress } = await market.getPositionAddress( + protocol.programAuthority, + positionId + ); + + const positionCount = ( + await market.getPositionList(protocol.programAuthority) + ).head; + const lastPositionId = positionCount == 0 ? 0 : positionCount - 1; + const lastPosition = await market.getPosition( + protocol.programAuthority, + lastPositionId + ); + const lastPositionPool = await market.getPoolByAddress(lastPosition.pool); + const [lastPositionLpPool] = protocol.getLpPoolAddressAndBump( + new Pair(lastPositionPool.tokenX, lastPositionPool.tokenY, { + fee: new BN(lastPositionPool.fee.v), + tickSpacing: lastPositionPool.tickSpacing, + }) + ); + + const { positionAddress: lastPositionAddress } = + await market.getPositionAddress( + protocol.programAuthority, + lastPositionId + ); + + const positionBeforeLastBurn = await market.getPosition( + protocol.programAuthority, + 1 + ); + await protocol.burnLpToken( + { + pair, + invariant: market, + + liquidityDelta: positionBeforeLastBurn.liquidity.v.div(new BN(2)), + + lastPositionLpPool, + + position: positionAddress, + lastPosition: lastPositionAddress, + accountX: userTokenXAccountAddress, + accountY: userTokenYAccountAddress, + }, + owner + ); + + const lpPool = await protocol.getLpPool(pair); + assert.ok(lpPool.positionExists); + + await market.getPosition(protocol.programAuthority, 1); + + const newFirstPositionLpPool = await protocol.getLpPool(pair); + assert.ok(newFirstPositionLpPool.positionExists); + }); + + it("mint one LpToken [pair 12]:[position 0]", async () => { + const pair = pair12; + + const userTokenXAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenX, + owner.publicKey + ); + const userTokenYAccount = await getOrCreateAssociatedTokenAccount( + connection, + owner, + pair.tokenY, + owner.publicKey + ); + + const positionId = 0; + let position = await market.getPosition( + protocol.programAuthority, + positionId + ); + + const [tokenLp] = protocol.getLpTokenAddressAndBump(pair); + const accountLpAddress = getAssociatedTokenAddressSync( + tokenLp, + owner.publicKey, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + const accountLp = await getAccount( + connection, + accountLpAddress, + undefined, + TOKEN_2022_PROGRAM_ID + ); + + const supplyBefore = accountLp.amount; + + const liquidityDelta = lpTokenAmountToLiquidity( + { v: new BN(accountLp.amount) }, + position.liquidity, + { v: new BN(1) }, + true + ); + + const { positionAddress } = await market.getPositionAddress( + protocol.programAuthority, + positionId + ); + + await protocol.mintLpToken( + { + liquidityDelta: liquidityDelta.v, + pair, + invariant: market, + + position: positionAddress, + accountX: userTokenXAccount.address, + accountY: userTokenYAccount.address, + }, + owner + ); + + position = await market.getPosition(protocol.programAuthority, positionId); + assert.ok(position); + + const supply = ( + await getAccount( + connection, + accountLpAddress, + undefined, + TOKEN_2022_PROGRAM_ID + ) + ).amount; + assert.equal(supply, supplyBefore + 1n); + }); +}); From 6548fc120e2505e7647575fb8b9db8a056e5a090 Mon Sep 17 00:00:00 2001 From: DevRozaDev <158298065+DevRozaDev@users.noreply.github.com> Date: Fri, 27 Sep 2024 19:33:12 +0200 Subject: [PATCH 2/2] deploy new backend --- .github/workflows/deploy-master.yml | 42 +++++++++++++-------------- protocol/Anchor.toml | 2 +- protocol/build.sh | 6 ++-- protocol/package-lock.json | 2 +- protocol/package.json | 2 +- protocol/programs/protocol/src/lib.rs | 2 +- protocol/sdk/package.json | 2 +- protocol/sdk/src/network.ts | 9 +++--- 8 files changed, 33 insertions(+), 34 deletions(-) diff --git a/.github/workflows/deploy-master.yml b/.github/workflows/deploy-master.yml index 82fb7ba..1a627b7 100644 --- a/.github/workflows/deploy-master.yml +++ b/.github/workflows/deploy-master.yml @@ -1,23 +1,23 @@ -name: deploy master +# name: deploy master -on: - push: - branches: [master] +# on: +# push: +# branches: [master] -jobs: - upload: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Deploy to Vercel Action - uses: BetaHuhn/deploy-to-vercel-action@v1 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - VERCEL_SCOPE: ${{ secrets.VERCEL_ORG_ID }} - WORKING_DIRECTORY: frontend - ALIAS_DOMAINS: | - link.invariant.app +# jobs: +# upload: +# runs-on: ubuntu-latest +# steps: +# - name: Checkout +# uses: actions/checkout@v2 +# - name: Deploy to Vercel Action +# uses: BetaHuhn/deploy-to-vercel-action@v1 +# with: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} +# VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} +# VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} +# VERCEL_SCOPE: ${{ secrets.VERCEL_ORG_ID }} +# WORKING_DIRECTORY: frontend +# ALIAS_DOMAINS: | +# link.invariant.app diff --git a/protocol/Anchor.toml b/protocol/Anchor.toml index 49e0576..3334e57 100644 --- a/protocol/Anchor.toml +++ b/protocol/Anchor.toml @@ -5,7 +5,7 @@ seeds = false skip-lint = false [programs.localnet] -protocol = "HTBzkQCWc2sbkn5WmLkPmQKKotaeeWgZ3RSD4Eg3f1MS" +protocol = "FE56ivh6V5JXW9nGuRV6DCWccXntLX8h4gtCqeJDwLZ8" invariant = "CsT21LCRqBfh4SCcNZXtWjRZ6xvYKvdpEBaytCVmWnVJ" [registry] diff --git a/protocol/build.sh b/protocol/build.sh index af1c321..7f1d1cc 100755 --- a/protocol/build.sh +++ b/protocol/build.sh @@ -1,13 +1,13 @@ set -e # Define the keypair JSON content -protocol='[125,25,103,8,46,252,74,11,10,231,221,13,113,82,123,17,118,205,218,140,247,37,159,150,140,109,50,158,185,90,57,107,244,112,134,82,19,192,92,70,188,247,227,156,239,127,119,76,193,85,143,146,12,43,48,189,79,193,48,21,49,108,226,209]' -invarinat='[100,94,104,217,253,66,112,71,155,206,9,70,30,6,93,185,82,49,169,250,96,24,120,234,25,145,148,106,134,60,55,123,176,93,3,185,31,73,255,255,107,72,82,123,112,9,31,16,214,15,171,107,164,37,143,208,35,195,133,157,224,137,246,197]' +protocol='[241,48,9,154,226,69,2,237,110,236,42,215,101,9,180,54,188,33,143,1,23,195,191,216,97,134,168,119,88,143,212,134,211,92,205,71,53,55,43,4,243,58,249,177,89,205,83,154,251,68,50,69,145,195,183,45,251,29,209,130,116,26,126,3]' +invariant='[100,94,104,217,253,66,112,71,155,206,9,70,30,6,93,185,82,49,169,250,96,24,120,234,25,145,148,106,134,60,55,123,176,93,3,185,31,73,255,255,107,72,82,123,112,9,31,16,214,15,171,107,164,37,143,208,35,195,133,157,224,137,246,197]' # Write the keypair JSON to file mkdir -p target/deploy echo "$protocol" > target/deploy/protocol-keypair.json -echo "$invarinat" > target/deploy/invariant-keypair.json +echo "$invariant" > target/deploy/invariant-keypair.json # Build the program using Anchor anchor build diff --git a/protocol/package-lock.json b/protocol/package-lock.json index d04adec..f67e4c7 100644 --- a/protocol/package-lock.json +++ b/protocol/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "@coral-xyz/anchor": "0.29.0", - "@invariant-labs/sdk-eclipse": "^0.0.17", + "@invariant-labs/sdk-eclipse": "=0.0.17", "@project-serum/serum": "^0.13.65", "@solana/spl-token": "^0.4.3", "@solana/web3.js": "^1.91.4" diff --git a/protocol/package.json b/protocol/package.json index 704f38f..2f748ab 100644 --- a/protocol/package.json +++ b/protocol/package.json @@ -23,7 +23,7 @@ "license": "ISC", "dependencies": { "@coral-xyz/anchor": "0.29.0", - "@invariant-labs/sdk-eclipse": "^0.0.17", + "@invariant-labs/sdk-eclipse": "=0.0.17", "@project-serum/serum": "^0.13.65", "@solana/spl-token": "^0.4.3", "@solana/web3.js": "^1.91.4" diff --git a/protocol/programs/protocol/src/lib.rs b/protocol/programs/protocol/src/lib.rs index 6b1da7b..3c25d2d 100644 --- a/protocol/programs/protocol/src/lib.rs +++ b/protocol/programs/protocol/src/lib.rs @@ -13,7 +13,7 @@ pub use program_id::*; mod program_id { use anchor_lang::prelude::*; - declare_id!("HTBzkQCWc2sbkn5WmLkPmQKKotaeeWgZ3RSD4Eg3f1MS"); + declare_id!("FE56ivh6V5JXW9nGuRV6DCWccXntLX8h4gtCqeJDwLZ8"); } #[macro_export] diff --git a/protocol/sdk/package.json b/protocol/sdk/package.json index 22ad78c..cf48177 100644 --- a/protocol/sdk/package.json +++ b/protocol/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@invariant-labs/eclipse-link-sdk", - "version": "0.1.11", + "version": "0.1.12", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { diff --git a/protocol/sdk/src/network.ts b/protocol/sdk/src/network.ts index c1a3a80..c00a582 100644 --- a/protocol/sdk/src/network.ts +++ b/protocol/sdk/src/network.ts @@ -8,15 +8,14 @@ export enum Network { export const getProtocolProgramAddress = (network: Network): string => { switch (network) { case Network.LOCAL: - return "HTBzkQCWc2sbkn5WmLkPmQKKotaeeWgZ3RSD4Eg3f1MS"; + return "FE56ivh6V5JXW9nGuRV6DCWccXntLX8h4gtCqeJDwLZ8"; case Network.DEV: - return "HTBzkQCWc2sbkn5WmLkPmQKKotaeeWgZ3RSD4Eg3f1MS"; + return "FE56ivh6V5JXW9nGuRV6DCWccXntLX8h4gtCqeJDwLZ8"; case Network.TEST: - return "HTBzkQCWc2sbkn5WmLkPmQKKotaeeWgZ3RSD4Eg3f1MS"; + return "FE56ivh6V5JXW9nGuRV6DCWccXntLX8h4gtCqeJDwLZ8"; case Network.MAIN: - return "HTBzkQCWc2sbkn5WmLkPmQKKotaeeWgZ3RSD4Eg3f1MS"; + return "FE56ivh6V5JXW9nGuRV6DCWccXntLX8h4gtCqeJDwLZ8"; default: throw new Error("Unknown network"); } }; -