diff --git a/Cargo.lock b/Cargo.lock index 637cf31..f9a1cdc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,9 +23,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" @@ -117,9 +117,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "ark-bn254" @@ -308,34 +308,33 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.23" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07" +checksum = "977eb15ea9efd848bb8a4a1a2500347ed7f0bf794edf0dc3ddcf439f43d36b23" dependencies = [ - "brotli", - "flate2", + "compression-codecs", + "compression-core", "futures-core", - "memchr", "pin-project-lite", "tokio", ] [[package]] name = "async-lock" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.4.1", "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", @@ -519,9 +518,9 @@ dependencies = [ [[package]] name = "brotli" -version = "8.0.1" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -657,7 +656,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -693,6 +692,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "compression-codecs" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "485abf41ac0c8047c07c87c72c8fb3eb5197f6e9d7ded615dfd1a00ae00a0f64" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -788,9 +805,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -977,9 +994,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" dependencies = [ "powerfmt", ] @@ -1128,9 +1145,9 @@ dependencies = [ [[package]] name = "enum-iterator-derive" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" +checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" dependencies = [ "proc-macro2", "quote", @@ -1151,9 +1168,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -1166,20 +1183,20 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.4.1", "pin-project-lite", ] [[package]] name = "fastbloom" -version = "0.9.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27cea6e7f512d43b098939ff4d5a5d6fe3db07971e1d05176fe26c642d33f5b8" +checksum = "18c1ddb9231d8554c2d6bdf4cfaabf0c59251658c68b6c95cd52dd0c513a912a" dependencies = [ "getrandom 0.3.3", - "rand 0.9.1", + "libm", + "rand 0.9.2", "siphasher 1.0.1", - "wide", ] [[package]] @@ -1223,9 +1240,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -1254,9 +1271,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -1413,7 +1430,7 @@ dependencies = [ "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.7+wasi-0.2.4", "wasm-bindgen", ] @@ -1445,9 +1462,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" dependencies = [ "bytes", "fnv", @@ -1504,9 +1521,9 @@ checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hermit-abi" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "histogram" @@ -1618,9 +1635,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1734,9 +1751,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -1785,6 +1802,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -1898,6 +1926,12 @@ version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libsecp256k1" version = "0.6.0" @@ -2034,9 +2068,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -2315,9 +2349,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.5.0+3.5.0" +version = "300.5.2+3.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f" +checksum = "d270b79e2926f5150189d475bc7e9d2c69f9c4697b185fa917d5a32b792d21b4" dependencies = [ "cc", ] @@ -2495,9 +2529,9 @@ checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] @@ -2555,9 +2589,9 @@ dependencies = [ [[package]] name = "quanta" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" dependencies = [ "crossbeam-utils", "libc", @@ -2570,9 +2604,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", @@ -2580,7 +2614,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.27", + "rustls 0.23.31", "socket2", "thiserror 2.0.12", "tokio", @@ -2590,18 +2624,18 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", "fastbloom", "getrandom 0.3.3", "lru-slab", - "rand 0.9.1", + "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.27", + "rustls 0.23.31", "rustls-pki-types", "rustls-platform-verifier", "slab", @@ -2613,16 +2647,16 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.12" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2636,9 +2670,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" @@ -2666,9 +2700,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -2742,18 +2776,18 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.5.0" +version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ "bitflags 2.9.0", ] [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -2761,9 +2795,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -2780,9 +2814,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", @@ -2792,9 +2826,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", @@ -2803,9 +2837,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "reqwest" @@ -2882,9 +2916,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -2924,14 +2958,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.3", + "rustls-webpki 0.103.6", "subtle", "zeroize", ] @@ -2969,22 +3003,22 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.5.3" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" +checksum = "be59af91596cac372a6942530653ad0c3a246cdd491aaa9dcaee47f88d67d5a0" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", "jni", "log", "once_cell", - "rustls 0.23.27", + "rustls 0.23.31", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.3", + "rustls-webpki 0.103.6", "security-framework", "security-framework-sys", - "webpki-root-certs 0.26.11", + "webpki-root-certs", "windows-sys 0.59.0", ] @@ -3006,9 +3040,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" dependencies = [ "ring", "rustls-pki-types", @@ -3027,15 +3061,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" -[[package]] -name = "safe_arch" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" -dependencies = [ - "bytemuck", -] - [[package]] name = "same-file" version = "1.0.6" @@ -3047,11 +3072,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -3078,9 +3103,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640" dependencies = [ "bitflags 2.9.0", "core-foundation 0.10.1", @@ -3091,9 +3116,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -3240,9 +3265,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -3267,12 +3292,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -3998,7 +4020,7 @@ dependencies = [ "log", "quinn", "quinn-proto", - "rustls 0.23.27", + "rustls 0.23.31", "solana-connection-cache", "solana-measure", "solana-metrics", @@ -4321,7 +4343,7 @@ dependencies = [ "quinn", "quinn-proto", "rand 0.8.5", - "rustls 0.23.27", + "rustls 0.23.31", "smallvec", "socket2", "solana-measure", @@ -5289,12 +5311,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde", @@ -5304,15 +5325,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -5345,17 +5366,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -5410,9 +5433,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -5520,9 +5543,9 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unicode-xid" @@ -5639,11 +5662,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ - "wit-bindgen-rt", + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", ] [[package]] @@ -5739,18 +5771,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "0.26.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" -dependencies = [ - "webpki-root-certs 1.0.0", -] - -[[package]] -name = "webpki-root-certs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a83f7e1a9f8712695c03eabe9ed3fbca0feff0152f33f12593e5a6303cb1a4" +checksum = "4e4ffd8df1c57e87c325000a3d6ef93db75279dc3a231125aac571650f22b12a" dependencies = [ "rustls-pki-types", ] @@ -5770,16 +5793,6 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" -[[package]] -name = "wide" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22" -dependencies = [ - "bytemuck", - "safe_arch", -] - [[package]] name = "winapi" version = "0.3.9" @@ -5798,11 +5811,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -5813,13 +5826,13 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.2.0", "windows-result", "windows-strings", ] @@ -5848,26 +5861,32 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" dependencies = [ - "windows-link", + "windows-link 0.2.0", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" dependencies = [ - "windows-link", + "windows-link 0.2.0", ] [[package]] @@ -5906,6 +5925,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -5945,13 +5982,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link 0.1.3", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -5970,6 +6024,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -5988,6 +6048,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -6006,12 +6072,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -6030,6 +6108,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -6048,6 +6132,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -6066,6 +6156,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -6084,6 +6180,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.7.3" @@ -6104,13 +6206,10 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.0", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" @@ -6254,9 +6353,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -6294,9 +6393,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/api/src/consts.rs b/api/src/consts.rs index 47b74f8..d21748d 100644 --- a/api/src/consts.rs +++ b/api/src/consts.rs @@ -20,6 +20,9 @@ pub const MAX_SUPPLY: u64 = ONE_ORE * 5_000_000; /// The seed of the block account PDA. pub const BLOCK: &[u8] = b"block"; +/// The seed of the board account PDA. +pub const BOARD: &[u8] = b"board"; + /// The seed of the config account PDA. pub const CONFIG: &[u8] = b"config"; @@ -29,6 +32,9 @@ pub const MARKET: &[u8] = b"market"; /// The seed of the miner account PDA. pub const MINER: &[u8] = b"miner"; +/// The seed of the square account PDA. +pub const SQUARE: &[u8] = b"square"; + /// The seed of the treasury account PDA. pub const TREASURY: &[u8] = b"treasury"; diff --git a/api/src/event.rs b/api/src/event.rs index 5eb6b52..9f59a2e 100644 --- a/api/src/event.rs +++ b/api/src/event.rs @@ -1,10 +1,7 @@ use steel::*; -use crate::state::SwapDirection; - pub enum OreEvent { Reset = 0, - Swap = 1, Mine = 2, } @@ -30,58 +27,6 @@ pub struct ResetEvent { pub ts: i64, } -#[repr(C)] -#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] -pub struct SwapEvent { - /// The event discriminator. - pub disc: u64, - - /// The authority of the swap. - pub authority: Pubkey, - - /// The block id. - pub block_id: u64, - - /// Swap direction. - pub direction: u64, - - /// Amount of base tokens to transfer. - pub base_to_transfer: u64, - - /// Amount of quote tokens to transfer. - pub quote_to_transfer: u64, - - /// Amount of base tokens swapped via virtual limit order. - pub base_via_order: u64, - - /// Amount of quote tokens swapped via virtual limit order. - pub quote_via_order: u64, - - /// Amount of base tokens swapped via curve. - pub base_via_curve: u64, - - /// Amount of quote tokens swapped via curve. - pub quote_via_curve: u64, - - /// Amount of quote tokens taken in fees. - pub quote_fee: u64, - - /// Amount of base tokens in the market. - pub base_liquidity: u64, - - /// Amount of quote tokens in the market. - pub quote_liquidity: u64, - - /// Amount of hashpower the miner now has. - pub miner_hashpower: u64, - - /// Amount of hashpower the block now has. - pub block_hashpower: u64, - - /// The timestamp of the event. - pub ts: i64, -} - #[repr(C)] #[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] pub struct MineEvent { @@ -107,12 +52,5 @@ pub struct MineEvent { pub ts: i64, } -impl SwapEvent { - pub fn direction(&self) -> SwapDirection { - SwapDirection::try_from(self.direction as u8).unwrap() - } -} - event!(ResetEvent); -event!(SwapEvent); event!(MineEvent); diff --git a/api/src/instruction.rs b/api/src/instruction.rs index 314da29..d16aab4 100644 --- a/api/src/instruction.rs +++ b/api/src/instruction.rs @@ -5,20 +5,14 @@ use steel::*; pub enum OreInstruction { // User Claim = 0, - Log = 1, - Mine = 2, - Swap = 3, - Initialize = 4, - Open = 5, - Close = 6, - Reset = 7, + Initialize = 1, + InitializeSquare = 2, + Prospect = 3, + Reset = 4, // Admin SetAdmin = 8, - SetBlockDuration = 9, SetFeeCollector = 10, - SetFeeRate = 11, - SetSniperFeeDuration = 12, // Seeker ClaimSeeker = 13, @@ -32,13 +26,11 @@ pub struct Claim { #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct Open { - pub id: [u8; 8], -} +pub struct Initialize {} #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct Close {} +pub struct InitializeSquare {} #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] @@ -46,12 +38,10 @@ pub struct Reset {} #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct Initialize {} - -#[repr(C)] -#[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct Log {} - +pub struct Prospect { + pub amount: [u8; 8], + pub square_id: [u8; 8], +} #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct Mine { @@ -108,16 +98,10 @@ pub struct SetSniperFeeDuration { pub struct ClaimSeeker {} instruction!(OreInstruction, Claim); -instruction!(OreInstruction, Open); -instruction!(OreInstruction, Close); instruction!(OreInstruction, Reset); +instruction!(OreInstruction, Prospect); instruction!(OreInstruction, Initialize); -instruction!(OreInstruction, Log); -instruction!(OreInstruction, Mine); -instruction!(OreInstruction, Swap); +instruction!(OreInstruction, InitializeSquare); instruction!(OreInstruction, SetAdmin); -instruction!(OreInstruction, SetBlockDuration); instruction!(OreInstruction, SetFeeCollector); -instruction!(OreInstruction, SetFeeRate); -instruction!(OreInstruction, SetSniperFeeDuration); instruction!(OreInstruction, ClaimSeeker); diff --git a/api/src/sdk.rs b/api/src/sdk.rs index 0a5d0f3..a293fe1 100644 --- a/api/src/sdk.rs +++ b/api/src/sdk.rs @@ -7,36 +7,22 @@ use crate::{ state::*, }; -pub fn log(signer: Pubkey, msg: &[u8]) -> Instruction { - let mut data = Log {}.to_bytes(); - data.extend_from_slice(msg); - Instruction { - program_id: crate::ID, - accounts: vec![AccountMeta::new(signer, true)], - data: data, - } -} - -pub fn program_log(accounts: &[AccountInfo], msg: &[u8]) -> Result<(), ProgramError> { - invoke_signed(&log(*accounts[0].key, msg), accounts, &crate::ID, &[MARKET]) -} +// let [signer_info, board_info, config_info, mint_info, treasury_info, vault_info, system_program, token_program, associated_token_program] = pub fn initialize(signer: Pubkey) -> Instruction { let config_address = config_pda().0; - let market_address = market_pda().0; + let board_address = board_pda().0; let mint_address = MINT_ADDRESS; let treasury_address = TREASURY_ADDRESS; - let treasury_tokens_address = treasury_tokens_address(); let vault_address = vault_address(); Instruction { program_id: crate::ID, accounts: vec![ AccountMeta::new(signer, true), + AccountMeta::new(board_address, false), AccountMeta::new(config_address, false), - AccountMeta::new(market_address, false), AccountMeta::new(mint_address, false), AccountMeta::new(treasury_address, false), - AccountMeta::new(treasury_tokens_address, false), AccountMeta::new(vault_address, false), AccountMeta::new_readonly(system_program::ID, false), AccountMeta::new_readonly(spl_token::ID, false), @@ -46,41 +32,41 @@ pub fn initialize(signer: Pubkey) -> Instruction { } } -pub fn mine(signer: Pubkey, id: u64, nonce: u64) -> Instruction { - let block_adddress = block_pda(id).0; - let miner_address = miner_pda(signer).0; - let market_address = market_pda().0; - Instruction { - program_id: crate::ID, - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(block_adddress, false), - AccountMeta::new(market_address, false), - AccountMeta::new(miner_address, false), - AccountMeta::new_readonly(crate::ID, false), - ], - data: Mine { - nonce: nonce.to_le_bytes(), - } - .to_bytes(), - } -} +// let [signer_info, square_info, system_program] = accounts else { -pub fn open(signer: Pubkey, id: u64) -> Instruction { - let block_adddress = block_pda(id).0; - let market_address = market_pda().0; +pub fn initialize_squares(signer: Pubkey) -> Instruction { Instruction { program_id: crate::ID, accounts: vec![ AccountMeta::new(signer, true), - AccountMeta::new(block_adddress, false), - AccountMeta::new(market_address, false), AccountMeta::new_readonly(system_program::ID, false), + AccountMeta::new(square_pda(0).0, false), + AccountMeta::new(square_pda(1).0, false), + AccountMeta::new(square_pda(2).0, false), + AccountMeta::new(square_pda(3).0, false), + AccountMeta::new(square_pda(4).0, false), + AccountMeta::new(square_pda(5).0, false), + AccountMeta::new(square_pda(6).0, false), + AccountMeta::new(square_pda(7).0, false), + AccountMeta::new(square_pda(8).0, false), + AccountMeta::new(square_pda(9).0, false), + AccountMeta::new(square_pda(10).0, false), + AccountMeta::new(square_pda(11).0, false), + AccountMeta::new(square_pda(12).0, false), + AccountMeta::new(square_pda(13).0, false), + AccountMeta::new(square_pda(14).0, false), + AccountMeta::new(square_pda(15).0, false), + AccountMeta::new(square_pda(16).0, false), + AccountMeta::new(square_pda(17).0, false), + AccountMeta::new(square_pda(18).0, false), + AccountMeta::new(square_pda(19).0, false), + AccountMeta::new(square_pda(20).0, false), + AccountMeta::new(square_pda(21).0, false), + AccountMeta::new(square_pda(22).0, false), + AccountMeta::new(square_pda(23).0, false), + AccountMeta::new(square_pda(24).0, false), ], - data: Open { - id: id.to_le_bytes(), - } - .to_bytes(), + data: InitializeSquare {}.to_bytes(), } } @@ -107,106 +93,63 @@ pub fn claim(signer: Pubkey, amount: u64) -> Instruction { } } -pub fn close(signer: Pubkey, opener: Pubkey, winner: Pubkey, id: u64) -> Instruction { - let block_adddress = block_pda(id).0; - let market_address = market_pda().0; - let miner_address = miner_pda(winner).0; - let miner_tokens_address = get_associated_token_address(&miner_address, &MINT_ADDRESS); +// let [signer_info, board_info, mint_info, treasury_info, reserve_tokens_info, vault_info, system_program, token_program, slot_hashes_sysvar] = + +pub fn reset(signer: Pubkey, miners: Vec) -> Instruction { + let board_address = board_pda().0; let mint_address = MINT_ADDRESS; let treasury_address = TREASURY_ADDRESS; - let treasury_tokens_address = treasury_tokens_address(); - Instruction { - program_id: crate::ID, - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(block_adddress, false), - AccountMeta::new(miner_address, false), - AccountMeta::new_readonly(market_address, false), - AccountMeta::new(miner_tokens_address, false), - AccountMeta::new(mint_address, false), - AccountMeta::new(opener, false), - AccountMeta::new(treasury_address, false), - AccountMeta::new(treasury_tokens_address, false), - AccountMeta::new_readonly(system_program::ID, false), - AccountMeta::new_readonly(spl_token::ID, false), - AccountMeta::new_readonly(spl_associated_token_account::ID, false), - ], - data: Close {}.to_bytes(), - } -} - -// let [signer_info, block_prev_info, block_next_info, config_info, market_info, mint_info, reserve_tokens_info, treasury_info, treasury_tokens_info, vault_info, system_program, token_program, ore_program, slot_hashes_sysvar] = - -pub fn reset(signer: Pubkey, id: u64) -> Instruction { - let block_prev_adddress = block_pda(id).0; - let block_next_adddress = block_pda(id + 1).0; - let config_address = config_pda().0; - let market_address = market_pda().0; - let mint_address = MINT_ADDRESS; - let treasury_address = TREASURY_ADDRESS; - let treasury_tokens_address = treasury_tokens_address(); + let reserve_tokens_address = BOOST_RESERVE_TOKEN; let vault_address = vault_address(); + let mut accounts = vec![ + AccountMeta::new(signer, true), + AccountMeta::new(board_address, false), + AccountMeta::new(mint_address, false), + AccountMeta::new(treasury_address, false), + AccountMeta::new(reserve_tokens_address, false), + AccountMeta::new(vault_address, false), + AccountMeta::new_readonly(system_program::ID, false), + AccountMeta::new_readonly(spl_token::ID, false), + AccountMeta::new_readonly(sysvar::slot_hashes::ID, false), + ]; + for miner in miners { + if miner != Pubkey::default() { + accounts.push(AccountMeta::new(miner_pda(miner).0, false)); + } + } Instruction { program_id: crate::ID, - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(block_prev_adddress, false), - AccountMeta::new(block_next_adddress, false), - AccountMeta::new_readonly(config_address, false), - AccountMeta::new(market_address, false), - AccountMeta::new(mint_address, false), - AccountMeta::new(BOOST_RESERVE_TOKEN, false), - AccountMeta::new(treasury_address, false), - AccountMeta::new(treasury_tokens_address, false), - AccountMeta::new(vault_address, false), - AccountMeta::new_readonly(system_program::ID, false), - AccountMeta::new_readonly(spl_token::ID, false), - AccountMeta::new_readonly(crate::ID, false), - AccountMeta::new_readonly(sysvar::slot_hashes::ID, false), - ], + accounts, data: Reset {}.to_bytes(), } } -// let [signer_info, block_info, config_info, fee_collector_info, market_info, miner_info, mint_info, tokens_info, vault_info, system_program, token_program, associated_token_program, ore_program] = -pub fn swap( - signer: Pubkey, - id: u64, - fee_collector: Pubkey, - amount: u64, - direction: SwapDirection, - precision: SwapPrecision, - seed: [u8; 32], -) -> Instruction { - let block_adddress = block_pda(id).0; +// let [signer_info, board_info, config_info, fee_collector_info, miner_info, mint_info, sender_info, square_info, vault_info, system_program, token_program, associated_token_program] = + +pub fn prospect(signer: Pubkey, fee_collector: Pubkey, amount: u64, square_id: u64) -> Instruction { + let board_address = board_pda().0; let config_address = config_pda().0; - let market_address = market_pda().0; let miner_address = miner_pda(signer).0; - let tokens_address = get_associated_token_address(&signer, &MINT_ADDRESS); + let sender_address = get_associated_token_address(&signer, &MINT_ADDRESS); + let square_address = square_pda(square_id).0; let vault_address = vault_address(); Instruction { program_id: crate::ID, accounts: vec![ AccountMeta::new(signer, true), - AccountMeta::new(block_adddress, false), + AccountMeta::new(board_address, false), AccountMeta::new(config_address, false), AccountMeta::new(fee_collector, false), - AccountMeta::new(market_address, false), AccountMeta::new(miner_address, false), - AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(tokens_address, false), + AccountMeta::new(sender_address, false), + AccountMeta::new(square_address, false), AccountMeta::new(vault_address, false), AccountMeta::new_readonly(system_program::ID, false), AccountMeta::new_readonly(spl_token::ID, false), - AccountMeta::new_readonly(spl_associated_token_account::ID, false), - AccountMeta::new_readonly(crate::ID, false), - AccountMeta::new_readonly(sysvar::slot_hashes::ID, false), ], - data: Swap { + data: Prospect { amount: amount.to_le_bytes(), - direction: direction as u8, - precision: precision as u8, - seed: seed, + square_id: square_id.to_le_bytes(), } .to_bytes(), } @@ -228,22 +171,6 @@ pub fn set_admin(signer: Pubkey, admin: Pubkey) -> Instruction { } } -pub fn set_block_duration(signer: Pubkey, block_duration: u64) -> Instruction { - let config_address = config_pda().0; - Instruction { - program_id: crate::ID, - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(config_address, false), - AccountMeta::new_readonly(system_program::ID, false), - ], - data: SetBlockDuration { - block_duration: block_duration.to_le_bytes(), - } - .to_bytes(), - } -} - pub fn set_fee_collector(signer: Pubkey, fee_collector: Pubkey) -> Instruction { let config_address = config_pda().0; Instruction { @@ -260,38 +187,6 @@ pub fn set_fee_collector(signer: Pubkey, fee_collector: Pubkey) -> Instruction { } } -pub fn set_fee_rate(signer: Pubkey, fee_rate: u64) -> Instruction { - let config_address = config_pda().0; - Instruction { - program_id: crate::ID, - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(config_address, false), - AccountMeta::new_readonly(system_program::ID, false), - ], - data: SetFeeRate { - fee_rate: fee_rate.to_le_bytes(), - } - .to_bytes(), - } -} - -pub fn set_sniper_fee_duration(signer: Pubkey, sniper_fee_duration: u64) -> Instruction { - let config_address = config_pda().0; - Instruction { - program_id: crate::ID, - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(config_address, false), - AccountMeta::new_readonly(system_program::ID, false), - ], - data: SetSniperFeeDuration { - sniper_fee_duration: sniper_fee_duration.to_le_bytes(), - } - .to_bytes(), - } -} - pub fn claim_seeker(signer: Pubkey, mint: Pubkey) -> Instruction { Instruction { program_id: crate::ID, diff --git a/api/src/state/block.rs b/api/src/state/board.rs similarity index 50% rename from api/src/state/block.rs rename to api/src/state/board.rs index 57a1a46..796d98b 100644 --- a/api/src/state/block.rs +++ b/api/src/state/board.rs @@ -1,27 +1,18 @@ use steel::*; -use crate::state::block_pda; +use crate::state::board_pda; use super::OreAccount; #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] -pub struct Block { - /// The block number. +pub struct Board { + /// The commits for the round. + pub commits: [u64; 25], + + /// The round number. pub id: u64, - /// The party that opened the block. - pub opener: Pubkey, - - /// The reward configuration. - pub reward: u64, - - /// The best hash submitted to the block. - pub best_hash: [u8; 32], - - /// The authority of the miner who submitted the best hash. - pub best_hash_miner: Pubkey, - /// The timestamp at which the block starts mining. pub start_at: i64, @@ -34,14 +25,20 @@ pub struct Block { /// The hash of the end slot, provided by solana, used for random number generation. pub slot_hash: [u8; 32], - /// The total amount of hashpower bought in the block. - pub total_hashpower: u64, + /// The total amount of ORE burned for the round. + pub total_burned: u64, + + /// The total amount of ORE committed for the round. + pub total_commits: u64, + + /// The total amount of ORE won by miners for the round. + pub total_winnings: u64, } -impl Block { +impl Board { pub fn pda(&self) -> (Pubkey, u8) { - block_pda(self.id) + board_pda() } } -account!(OreAccount, Block); +account!(OreAccount, Board); diff --git a/api/src/state/market/buy_exact_in.rs b/api/src/state/market/buy_exact_in.rs deleted file mode 100644 index 55db73b..0000000 --- a/api/src/state/market/buy_exact_in.rs +++ /dev/null @@ -1,87 +0,0 @@ -use steel::Pubkey; - -use crate::{error::OreError, event::OreEvent}; - -use super::{Market, SwapDirection, TokenType, VirtualLimitOrder}; -use crate::event::SwapEvent; - -impl Market { - pub fn buy_exact_in(&mut self, quote_in: u64) -> Result { - // Get fee from quote side. - let quote_fee = self.fee(quote_in); - let quote_in_post_fee = quote_in - quote_fee; - - // Upcast data. - let quote_in_post_fee = quote_in_post_fee as u128; - - // Get virtual limit order. - let VirtualLimitOrder { - size_in_base: ask_size_in_base, - size_in_quote: ask_size_in_quote, - } = self.get_virtual_limit_order(SwapDirection::Buy); - - // Execute swap. - let (base_via_ask, quote_via_ask, base_via_curve, quote_via_curve) = - if !self.sandwich_resistance_enabled() { - // Fill entire swap via curve. - let quote_via_curve = quote_in_post_fee; - let base_via_curve = self.get_base_out(quote_via_curve); - self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Buy)?; - (0, 0, base_via_curve, quote_via_curve) - } else if ask_size_in_quote >= quote_in_post_fee { - // Fill entire swap via virtual limit order. - let quote_via_ask = quote_in_post_fee; - let base_via_ask = self.get_complementary_limit_order_size( - quote_in_post_fee, - SwapDirection::Buy, - TokenType::Quote, - ); - self.update_reserves(base_via_ask, quote_via_ask, SwapDirection::Buy)?; - (base_via_ask, quote_via_ask, 0, 0) - } else { - // Partially fill swap via virtual limit order. - let base_via_ask = ask_size_in_base; - let quote_via_ask = ask_size_in_quote; - self.update_reserves(base_via_ask, quote_via_ask, SwapDirection::Buy)?; - - // Fill remaining swap amount via curve. - let quote_via_curve = quote_in_post_fee - ask_size_in_quote; - let base_via_curve = self.get_base_out(quote_via_curve); - self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Buy)?; - (base_via_ask, quote_via_ask, base_via_curve, quote_via_curve) - }; - - // Produce swap result. - let base_out = base_via_ask + base_via_curve; - let swap_event = SwapEvent { - disc: OreEvent::Swap as u64, - authority: Pubkey::default(), - block_id: 0, - direction: SwapDirection::Buy as u64, - base_to_transfer: base_out as u64, - quote_to_transfer: quote_in, - base_via_order: base_via_ask as u64, - quote_via_order: quote_via_ask as u64, - base_via_curve: base_via_curve as u64, - quote_via_curve: quote_via_curve as u64, - quote_fee: quote_fee as u64, - base_liquidity: self.base.liquidity() as u64, - quote_liquidity: self.quote.liquidity() as u64, - miner_hashpower: 0, - block_hashpower: 0, - ts: 0, - }; - - // Sanity check swap event. - assert!( - swap_event.base_to_transfer == swap_event.base_via_order + swap_event.base_via_curve - ); - assert!( - swap_event.quote_to_transfer - == swap_event.quote_via_order + swap_event.quote_via_curve + swap_event.quote_fee - ); - - // Return - Ok(swap_event) - } -} diff --git a/api/src/state/market/buy_exact_out.rs b/api/src/state/market/buy_exact_out.rs deleted file mode 100644 index b7b55d8..0000000 --- a/api/src/state/market/buy_exact_out.rs +++ /dev/null @@ -1,92 +0,0 @@ -use steel::Pubkey; - -use crate::{error::OreError, event::OreEvent}; - -use super::{Market, SwapDirection, TokenType, VirtualLimitOrder}; -use crate::event::SwapEvent; - -impl Market { - pub fn buy_exact_out(&mut self, base_out: u64) -> Result { - // Check if there is enough liquidity. - if self.base.balance < base_out { - return Err(OreError::InsufficientLiquidity); - } - - // Upcast data. - let base_out = base_out as u128; - - // Get virtual limit order. - let VirtualLimitOrder { - size_in_base: ask_size_in_base, - size_in_quote: ask_size_in_quote, - } = self.get_virtual_limit_order(SwapDirection::Buy); - - // Execute swap. - let (base_via_ask, quote_via_ask, base_via_curve, quote_via_curve) = - if !self.sandwich_resistance_enabled() { - // Fill entire swap via curve. - let base_via_curve = base_out; - let quote_via_curve = self.get_quote_in(base_via_curve)?; - self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Buy)?; - (0, 0, base_via_curve, quote_via_curve) - } else if ask_size_in_base >= base_out { - // Fill entire swap through virtual limit order. - let base_via_ask = base_out; - let quote_via_ask = self.get_complementary_limit_order_size( - base_via_ask, - SwapDirection::Buy, - TokenType::Base, - ); - self.update_reserves(base_via_ask, quote_via_ask, SwapDirection::Buy)?; - (base_via_ask, quote_via_ask, 0, 0) - } else { - // Partially fill swap through virtual limit order. - let base_via_ask = ask_size_in_base; - let quote_via_ask = ask_size_in_quote; - self.update_reserves(base_via_ask, quote_via_ask, SwapDirection::Buy)?; - - // Fill remaining swap amount through pool. - let base_via_curve = base_out - base_via_ask; - let quote_via_curve = self.get_quote_in(base_via_curve)?; - self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Buy)?; - (base_via_ask, quote_via_ask, base_via_curve, quote_via_curve) - }; - - // Calculate fee. - let quote_post_fee = quote_via_ask + quote_via_curve; - let quote_in = self.pre_fee(quote_post_fee as u64) as u128; - let quote_fee = quote_in - quote_post_fee; - - // Produce swap result. - let swap_event = SwapEvent { - disc: OreEvent::Swap as u64, - authority: Pubkey::default(), - block_id: 0, - direction: SwapDirection::Buy as u64, - base_to_transfer: base_out as u64, - quote_to_transfer: quote_in as u64, - base_via_order: base_via_ask as u64, - quote_via_order: quote_via_ask as u64, - base_via_curve: base_via_curve as u64, - quote_via_curve: quote_via_curve as u64, - quote_fee: quote_fee as u64, - base_liquidity: self.base.liquidity() as u64, - quote_liquidity: self.quote.liquidity() as u64, - miner_hashpower: 0, - block_hashpower: 0, - ts: 0, - }; - - // Sanity check swap result. - assert!( - swap_event.base_to_transfer == swap_event.base_via_order + swap_event.base_via_curve - ); - assert!( - swap_event.quote_to_transfer - == swap_event.quote_via_order + swap_event.quote_via_curve + swap_event.quote_fee - ); - - // Return. - Ok(swap_event) - } -} diff --git a/api/src/state/market/curve.rs b/api/src/state/market/curve.rs deleted file mode 100644 index 6ac1da9..0000000 --- a/api/src/state/market/curve.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::error::OreError; - -use super::Market; - -// TODO Add weights. - -impl Market { - /// Returns the constant product invariant. - pub(crate) fn k(&self) -> u128 { - (self.base.liquidity() * self.quote.liquidity()).saturating_sub(1) - } - - /// Returns the amount of base tokens that can be bought from a given amount of quote tokens. - pub fn get_base_out(&self, quote_in: u128) -> u128 { - let base_out = self.base.liquidity() - - (self.k() / (self.quote.liquidity() + quote_in)).saturating_add(1); - base_out - } - - /// Returns the amount of quote tokens received from selling a given amount of base tokens. - pub fn get_quote_out(&self, base_in: u128) -> u128 { - let quote_out = self.quote.liquidity() - - (self.k() / (self.base.liquidity() + base_in)).saturating_add(1); - quote_out - } - - /// Returns the amount of quote tokens needed to buy a given amount of base tokens. - pub fn get_quote_in(&self, base_out: u128) -> Result { - if base_out >= self.base.liquidity() { - return Err(OreError::InsufficientVaultReserves.into()); - } - let quote_in = (self.k() / (self.base.liquidity() - base_out)).saturating_add(1) - - self.quote.liquidity(); - Ok(quote_in) - } - - /// Returns the amount of base tokens which must be sold to receive a given amount of quote tokens. - pub fn get_base_in(&self, quote_out: u128) -> Result { - if quote_out >= self.quote.liquidity() { - return Err(OreError::InsufficientVaultReserves.into()); - } - let base_in = (self.k() / (self.quote.liquidity() - quote_out)).saturating_add(1) - - self.base.liquidity(); - Ok(base_in) - } -} diff --git a/api/src/state/market/fees.rs b/api/src/state/market/fees.rs deleted file mode 100644 index 23c5986..0000000 --- a/api/src/state/market/fees.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::consts::*; - -use super::Market; - -impl Market { - pub(crate) fn apply_fees(&mut self, quote_fee: u64) { - // Process protocol fees. - self.fee.cumulative += quote_fee; - self.fee.uncollected += quote_fee; - } - - /// Calculates the fee from a quote amount. - pub(crate) fn fee(&self, quote_size: u64) -> u64 { - quote_size * self.fee.rate / DENOMINATOR_BPS - } - - /// Calculates the pre-fee quote from a post-fee quote amount. - pub(crate) fn pre_fee(&self, quote_post_fee: u64) -> u64 { - // x * 10000 / (10000 - fee) is approximately equivalent to x * (1 - fee / 10000) - let numerator = quote_post_fee * DENOMINATOR_BPS; - let denominator = DENOMINATOR_BPS - self.fee.rate; - return numerator / denominator; - } -} diff --git a/api/src/state/market/market.rs b/api/src/state/market/market.rs deleted file mode 100644 index 5d543c2..0000000 --- a/api/src/state/market/market.rs +++ /dev/null @@ -1,454 +0,0 @@ -use spl_associated_token_account::get_associated_token_address; -use steel::*; - -use crate::state::{market_pda, OreAccount}; - -// TODO Bonding curve stuff - -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] -pub struct Market { - /// Base token parameters. - pub base: TokenParams, - - /// Quote token parameters. - pub quote: TokenParams, - - /// Fee parameters. - pub fee: FeeParams, - - /// Snapshot of the market state at the time of the last swap. - pub snapshot: Snapshot, - - /// The id of the current block. - pub block_id: u64, -} - -impl Market { - pub fn pda(&self) -> (Pubkey, u8) { - market_pda() - } - - pub fn base_vault(&self) -> Pubkey { - get_associated_token_address(&self.pda().0, &self.base.mint) - } - - pub fn quote_vault(&self) -> Pubkey { - get_associated_token_address(&self.pda().0, &self.quote.mint) - } - - pub fn sandwich_resistance_enabled(&self) -> bool { - self.snapshot.enabled == 1 - } -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] -pub struct TokenParams { - /// Mint of the token. - pub mint: Pubkey, - - /// Amount of tokens held in liquidity. - pub balance: u64, - - /// Amount of virtual tokens held in liquidity. - pub balance_virtual: u64, -} - -impl TokenParams { - pub fn liquidity(&self) -> u128 { - (self.balance + self.balance_virtual) as u128 - } -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] -pub struct FeeParams { - /// Cumulative protocol fees. - pub cumulative: u64, - - /// Fee rate in basis points. - pub rate: u64, - - /// Current uncollected protocol fees. - pub uncollected: u64, -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] -pub struct Snapshot { - /// Whether sandwich resistance is enabled. - pub enabled: u64, - - /// Base token balance at the time of the snapshot. - pub base_balance: u64, - - /// Quote token balance at the time of the snapshot. - pub quote_balance: u64, - - /// Slot at which the snapshot was taken. - pub slot: u64, -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] -pub struct VirtualLimitOrder { - /// Size of the virtual limit order in base tokens. - pub size_in_base: u128, - - /// Size of the virtual limit order in quote tokens. - pub size_in_quote: u128, -} - -#[repr(u8)] -#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] -pub enum SwapDirection { - /// Swap quote tokens for base tokens. - Buy = 0, - - /// Swap base tokens for quote tokens. - Sell = 1, -} - -#[repr(u8)] -#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] -pub enum SwapPrecision { - /// Swap with precision exact in amount. - ExactIn = 0, - - /// Swap with precision exact out amount. - ExactOut = 1, -} - -#[repr(u8)] -#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] -pub enum TokenType { - /// Base token. - Base = 0, - - /// Quote token. - Quote = 1, -} - -account!(OreAccount, Market); - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn test_fees_buy_exact_in() { - let mut market = new_market(); - let swap = market - .swap( - 100_000, - SwapDirection::Buy, - SwapPrecision::ExactIn, - Clock::default(), - ) - .unwrap(); - assert_eq!(swap.quote_via_curve, 99_000); - assert_eq!(market.fee.uncollected, 1000); // Protocol fee is 1% - } - - #[test] - fn test_fees_sell_exact_in() { - let mut market = new_market(); - let swap = market - .swap( - 100_000, - SwapDirection::Sell, - SwapPrecision::ExactIn, - Clock::default(), - ) - .unwrap(); - assert_eq!(swap.quote_via_curve, 98_991); - assert_eq!(market.fee.uncollected, 999); - } - - #[test] - fn test_fees_buy_exact_out() { - let mut market = new_market(); - let swap = market - .swap( - 100_000, - SwapDirection::Buy, - SwapPrecision::ExactOut, - Clock::default(), - ) - .unwrap(); - assert_eq!(swap.quote_via_curve, 100_011); - assert_eq!(market.fee.uncollected, 1010); - } - - #[test] - fn test_fees_sell_exact_out() { - let mut market = new_market(); - let swap = market - .swap( - 100_000, - SwapDirection::Sell, - SwapPrecision::ExactOut, - Clock::default(), - ) - .unwrap(); - assert_eq!(swap.quote_via_curve, 101_010); - assert_eq!(market.fee.uncollected, 1010); - } - - #[test] - fn test_fills() { - let mut market = new_market(); - let mut clock = Clock::default(); - clock.slot = 10; - - // Small buy - // Assert swap is filled via curve. - // Post swap, price is above snapshot. - let swap_1 = market - .swap( - 100_000, - SwapDirection::Buy, - SwapPrecision::ExactIn, - clock.clone(), - ) - .unwrap(); - assert!(swap_1.base_via_curve > 0 && swap_1.quote_via_curve > 0); - assert!(swap_1.base_via_order == 0 && swap_1.quote_via_order == 0); - - // Large sell - // Assert swap is partially filled via order and partially filled via curve. - // Post swap, price is below snapshot. - let swap_2 = market - .swap( - 1_000_000, - SwapDirection::Sell, - SwapPrecision::ExactIn, - clock.clone(), - ) - .unwrap(); - assert!(swap_2.base_via_curve > 0 && swap_2.quote_via_curve > 0); - assert!(swap_2.base_via_order > 0 && swap_2.quote_via_order > 0); - - // Small buy - // Assert swap is filled via order - // Post swap, price is still below snapshot. - let swap_3 = market - .swap( - 1_000, - SwapDirection::Buy, - SwapPrecision::ExactIn, - clock.clone(), - ) - .unwrap(); - assert!(swap_3.base_via_curve == 0 && swap_3.quote_via_curve == 0); - assert!(swap_3.base_via_order > 0 && swap_3.quote_via_order > 0); - - // Large buy - // Assert swap is partially filled via order and partially filled via curve. - // Post swap, price is above snapshot. - let swap_4 = market - .swap( - 1_000_000, - SwapDirection::Buy, - SwapPrecision::ExactIn, - clock.clone(), - ) - .unwrap(); - assert!(swap_4.base_via_curve > 0 && swap_4.quote_via_curve > 0); - assert!(swap_4.base_via_order > 0 && swap_4.quote_via_order > 0); - } - - #[test] - fn test_sandwich() { - let mut market = new_market(); - market.fee.rate = 0; - market.snapshot = Snapshot { - enabled: 0, - base_balance: 0, - quote_balance: 0, - slot: 0, - }; - - let mut clock = Clock::default(); - clock.slot = 10; - - // Open sandwich - let swap_1 = market - .swap( - 100_000, - SwapDirection::Buy, - SwapPrecision::ExactIn, - clock.clone(), - ) - .unwrap(); - let amount_base_1 = swap_1.base_to_transfer; - assert!(swap_1.base_via_curve > 0 && swap_1.quote_via_curve > 0); - assert!(swap_1.base_via_order == 0 && swap_1.quote_via_order == 0); - - // Victim buys - let swap_2 = market - .swap( - 100_000, - SwapDirection::Buy, - SwapPrecision::ExactIn, - clock.clone(), - ) - .unwrap(); - assert!(swap_2.base_via_curve > 0 && swap_2.quote_via_curve > 0); - assert!(swap_2.base_via_order == 0 && swap_2.quote_via_order == 0); - - // Close sandwich - let swap_3 = market - .swap( - amount_base_1, - SwapDirection::Sell, - SwapPrecision::ExactIn, - clock.clone(), - ) - .unwrap(); - assert!(swap_3.base_via_curve > 0 && swap_3.quote_via_curve > 0); - assert!(swap_3.base_via_order == 0 && swap_3.quote_via_order == 0); - - // Assert sandwich attack succeeded - assert!(swap_3.quote_to_transfer > swap_1.quote_to_transfer); - } - - #[test] - fn test_sandwich_resistance() { - let mut market = new_market(); - market.fee.rate = 0; - - let mut clock = Clock::default(); - clock.slot = 10; - - // Open sandwich - let swap_1 = market - .swap( - 100_000, - SwapDirection::Buy, - SwapPrecision::ExactIn, - clock.clone(), - ) - .unwrap(); - let amount_base_1 = swap_1.base_to_transfer; - assert!(swap_1.base_via_curve > 0 && swap_1.quote_via_curve > 0); - assert!(swap_1.base_via_order == 0 && swap_1.quote_via_order == 0); - - // Victim buys - let swap_2 = market - .swap( - 100_000, - SwapDirection::Buy, - SwapPrecision::ExactIn, - clock.clone(), - ) - .unwrap(); - assert!(swap_2.base_via_curve > 0 && swap_2.quote_via_curve > 0); - assert!(swap_2.base_via_order == 0 && swap_2.quote_via_order == 0); - - // Close sandwich - let swap_3 = market - .swap( - amount_base_1, - SwapDirection::Sell, - SwapPrecision::ExactIn, - clock.clone(), - ) - .unwrap(); - assert!(swap_3.base_via_curve == 0 && swap_3.quote_via_curve == 0); - assert!(swap_3.base_via_order > 0 && swap_3.quote_via_order > 0); - - // Assert sandwich attack failed - assert!(swap_3.quote_to_transfer <= swap_1.quote_to_transfer); - } - - #[test] - fn test_virtual_liquidity() { - let mut market = new_market(); - market.fee.rate = 0; - market.quote.balance_virtual = 1_000_000_000; - market.quote.balance = 0; - - let mut clock = Clock::default(); - clock.slot = 10; - - // Sell - // Assert swap fails without real liquidity to satisfy order. - let swap = market.swap( - 100_000, - SwapDirection::Sell, - SwapPrecision::ExactIn, - clock.clone(), - ); - assert!(swap.is_err()); - - // Buy - // Assert buy succeeds adding liquidity. - let swap = market - .swap( - 100_000, - SwapDirection::Buy, - SwapPrecision::ExactIn, - clock.clone(), - ) - .unwrap(); - assert!(swap.base_via_curve > 0 && swap.quote_via_curve > 0); - assert!(swap.base_via_order == 0 && swap.quote_via_order == 0); - assert_eq!(market.quote.balance, 100_000); - assert_eq!(market.quote.balance_virtual, 1_000_000_000); - - // Sell - // Assert sell fails if there is insufficient liquidity. - let swap = market.swap( - 100_001, - SwapDirection::Sell, - SwapPrecision::ExactOut, - clock.clone(), - ); - assert!(swap.is_err()); - - // Sell - // Assert sell succeeds removing liquidity. - let swap = market - .swap( - 100_000, - SwapDirection::Sell, - SwapPrecision::ExactOut, - clock.clone(), - ) - .unwrap(); - assert!(swap.base_via_curve > 0 && swap.quote_via_curve > 0); - assert!(swap.base_via_order > 0 && swap.quote_via_order > 0); - assert_eq!(market.quote.balance, 0); - assert_eq!(market.quote.balance_virtual, 1_000_000_000); - } - - fn new_market() -> Market { - Market { - base: TokenParams { - mint: Pubkey::new_unique(), - balance: 1_000_000_000, - balance_virtual: 0, - }, - quote: TokenParams { - mint: Pubkey::new_unique(), - balance: 1_000_000_000, - balance_virtual: 0, - }, - fee: FeeParams { - cumulative: 0, - uncollected: 0, - rate: 100, // 100 bps - }, - snapshot: Snapshot { - enabled: 1, - base_balance: 0, - quote_balance: 0, - slot: 0, - }, - block_id: 0, - } - } -} diff --git a/api/src/state/market/mod.rs b/api/src/state/market/mod.rs deleted file mode 100644 index 6624648..0000000 --- a/api/src/state/market/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod buy_exact_in; -mod buy_exact_out; -mod curve; -mod fees; -mod market; -mod sell_exact_in; -mod sell_exact_out; -mod swap; -mod vaults; -mod virtual_limit_order; - -pub use market::*; diff --git a/api/src/state/market/sell_exact_in.rs b/api/src/state/market/sell_exact_in.rs deleted file mode 100644 index e41de69..0000000 --- a/api/src/state/market/sell_exact_in.rs +++ /dev/null @@ -1,95 +0,0 @@ -use steel::Pubkey; - -use crate::{error::OreError, event::OreEvent}; - -use super::{Market, SwapDirection, TokenType, VirtualLimitOrder}; -use crate::event::SwapEvent; - -impl Market { - pub fn sell_exact_in(&mut self, base_in: u64) -> Result { - // Get fee from quote side. - let mut quote_fee = 0; - - // Upcast data. - let base_in = base_in as u128; - - // Get virtual limit order. - let VirtualLimitOrder { - size_in_base: bid_size_in_base, - size_in_quote: bid_size_in_quote, - } = self.get_virtual_limit_order(SwapDirection::Sell); - - // Execute swap. - let (base_via_bid, quote_via_bid, base_via_curve, quote_via_curve) = - if !self.sandwich_resistance_enabled() { - // Fill entire swap via curve. - let base_via_curve = base_in; - let mut quote_via_curve = self.get_quote_out(base_via_curve); - self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Sell)?; - let swap_fee = self.fee(quote_via_curve as u64); - quote_fee += swap_fee; - quote_via_curve -= swap_fee as u128; - (0, 0, base_via_curve, quote_via_curve) - } else if bid_size_in_base >= base_in { - // Fill entire swap through virtual limit order. - let base_via_bid = base_in; - let mut quote_via_bid = self.get_complementary_limit_order_size( - base_in, - SwapDirection::Sell, - TokenType::Base, - ); - quote_fee += self.fee(quote_via_bid as u64); - self.update_reserves(base_via_bid, quote_via_bid, SwapDirection::Sell)?; - quote_via_bid -= quote_fee as u128; - (base_via_bid, quote_via_bid, 0, 0) - } else { - // Partially fill swap through virtual limit order. - let base_via_bid = bid_size_in_base; - let mut quote_via_bid = bid_size_in_quote; - quote_fee += self.fee(quote_via_bid as u64); - self.update_reserves(base_via_bid, quote_via_bid, SwapDirection::Sell)?; - quote_via_bid -= quote_fee as u128; - - // Fill remaining swap through pool. - let base_via_curve = base_in - base_via_bid; - let mut quote_via_curve = self.get_quote_out(base_via_curve); - self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Sell)?; - let swap_fee = self.fee(quote_via_curve as u64); - quote_fee += swap_fee; - quote_via_curve -= swap_fee as u128; - (base_via_bid, quote_via_bid, base_via_curve, quote_via_curve) - }; - - // Produce swap result. - let quote_out = quote_via_bid + quote_via_curve; - let swap_event = SwapEvent { - disc: OreEvent::Swap as u64, - authority: Pubkey::default(), - block_id: 0, - direction: SwapDirection::Sell as u64, - base_to_transfer: base_in as u64, - quote_to_transfer: quote_out as u64, - base_via_order: base_via_bid as u64, - quote_via_order: quote_via_bid as u64, - base_via_curve: base_via_curve as u64, - quote_via_curve: quote_via_curve as u64, - quote_fee: quote_fee as u64, - base_liquidity: self.base.liquidity() as u64, - quote_liquidity: self.quote.liquidity() as u64, - miner_hashpower: 0, - block_hashpower: 0, - ts: 0, - }; - - // Sanity check swap result. - assert!( - swap_event.base_to_transfer == swap_event.base_via_order + swap_event.base_via_curve - ); - assert!( - swap_event.quote_to_transfer == swap_event.quote_via_order + swap_event.quote_via_curve - ); - - // Return. - Ok(swap_event) - } -} diff --git a/api/src/state/market/sell_exact_out.rs b/api/src/state/market/sell_exact_out.rs deleted file mode 100644 index 38bac79..0000000 --- a/api/src/state/market/sell_exact_out.rs +++ /dev/null @@ -1,94 +0,0 @@ -use steel::Pubkey; - -use crate::{error::OreError, event::OreEvent}; - -use super::{Market, SwapDirection, TokenType, VirtualLimitOrder}; -use crate::event::SwapEvent; - -impl Market { - pub fn sell_exact_out(&mut self, quote_out: u64) -> Result { - // Check if there is enough liquidity. - if self.quote.balance < quote_out { - return Err(OreError::InsufficientLiquidity); - } - - // Calculate fee. - let quote_out_pre_fee = self.pre_fee(quote_out) as u128; - let quote_fee = quote_out_pre_fee - quote_out as u128; - - // Upcast data. - let quote_out = quote_out as u128; - - // Get virtual limit order. - let VirtualLimitOrder { - size_in_base: bid_size_in_base, - size_in_quote: bid_size_in_quote, - } = self.get_virtual_limit_order(SwapDirection::Sell); - - // Execute swap. - let (base_via_bid, quote_via_bid, base_via_curve, quote_via_curve) = - if !self.sandwich_resistance_enabled() { - // Fill entire swap via curve. - let quote_via_curve = quote_out_pre_fee; - let base_via_curve = self.get_base_in(quote_via_curve)?; - self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Sell)?; - (0, 0, base_via_curve, quote_via_curve) - } else if bid_size_in_quote >= quote_out { - // Fill entire swap through virtual limit order. - let quote_via_bid = quote_out_pre_fee; - let base_via_bid = self.get_complementary_limit_order_size( - quote_via_bid, - SwapDirection::Sell, - TokenType::Quote, - ); - self.update_reserves(base_via_bid, quote_via_bid, SwapDirection::Sell)?; - (base_via_bid, quote_via_bid, 0, 0) - } else { - // Partially fill swap through virtual limit order. - let base_via_bid = bid_size_in_base; - let quote_via_bid = bid_size_in_quote; - self.update_reserves(base_via_bid, quote_via_bid, SwapDirection::Sell)?; - - // Fill remaining swap amount through pool. - let quote_via_curve = quote_out_pre_fee - quote_via_bid; - let base_via_curve = self.get_base_in(quote_via_curve)?; - self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Sell)?; - (base_via_bid, quote_via_bid, base_via_curve, quote_via_curve) - }; - - // Calculate fee. - let base_in = base_via_bid + base_via_curve; - - // Produce swap result. - let swap_event = SwapEvent { - disc: OreEvent::Swap as u64, - authority: Pubkey::default(), - block_id: 0, - direction: SwapDirection::Sell as u64, - base_to_transfer: base_in as u64, - quote_to_transfer: quote_out as u64, - base_via_order: base_via_bid as u64, - quote_via_order: quote_via_bid as u64, - base_via_curve: base_via_curve as u64, - quote_via_curve: quote_via_curve as u64, - quote_fee: quote_fee as u64, - base_liquidity: self.base.liquidity() as u64, - quote_liquidity: self.quote.liquidity() as u64, - miner_hashpower: 0, - block_hashpower: 0, - ts: 0, - }; - - // Sanity check swap result. - assert!( - swap_event.base_to_transfer == swap_event.base_via_order + swap_event.base_via_curve - ); - assert!( - swap_event.quote_to_transfer - == swap_event.quote_via_order + swap_event.quote_via_curve - swap_event.quote_fee - ); - - // Return. - Ok(swap_event) - } -} diff --git a/api/src/state/market/swap.rs b/api/src/state/market/swap.rs deleted file mode 100644 index 0c3e2b9..0000000 --- a/api/src/state/market/swap.rs +++ /dev/null @@ -1,45 +0,0 @@ -use steel::Clock; - -use crate::error::OreError; - -use super::{Market, SwapDirection, SwapPrecision}; -use crate::event::SwapEvent; - -impl Market { - pub fn swap( - &mut self, - amount: u64, - direction: SwapDirection, - precision: SwapPrecision, - clock: Clock, - ) -> Result { - // Update snapshot. - self.update_snapshot(&clock); - - // Get invariant. - let k_pre = self.k(); - - // Execute swap. - let mut swap_event = match (direction, precision) { - (SwapDirection::Buy, SwapPrecision::ExactIn) => self.buy_exact_in(amount)?, - (SwapDirection::Buy, SwapPrecision::ExactOut) => self.buy_exact_out(amount)?, - (SwapDirection::Sell, SwapPrecision::ExactIn) => self.sell_exact_in(amount)?, - (SwapDirection::Sell, SwapPrecision::ExactOut) => self.sell_exact_out(amount)?, - }; - - // Update timestamp. - swap_event.ts = clock.unix_timestamp; - - // Check invariant. - let k_post = self.k(); - if k_pre > k_post { - return Err(OreError::InvariantViolation.into()); - } - - // Apply fees. - self.apply_fees(swap_event.quote_fee); - - // Return. - Ok(swap_event) - } -} diff --git a/api/src/state/market/vaults.rs b/api/src/state/market/vaults.rs deleted file mode 100644 index 4212125..0000000 --- a/api/src/state/market/vaults.rs +++ /dev/null @@ -1,43 +0,0 @@ -use solana_program::log::sol_log; -use steel::*; - -use crate::error::OreError; - -use super::Market; - -/// Vault reserve checks. -impl Market { - /// Sanity check that vaults have reserves for all market debts. - /// Assumes the token accounts have already been validated as the market's base and quote vaults. - // pub fn check_vaults( - // &self, - // base_vault: &TokenAccount, - // quote_vault: &TokenAccount, - // ) -> Result<(), OreError> { - // self.check_base_vault(base_vault)?; - // self.check_quote_vault(quote_vault)?; - // Ok(()) - // } - - /// Sanity check that base vault has reserves for all market debts. - /// Assumes the token account has already been validated as the market's base vault. - // pub fn check_base_vault(&self, base_vault: &TokenAccount) -> Result<(), OreError> { - // if base_vault.amount() < self.base.balance { - // sol_log(&format!("A base_vault.amount: {}", base_vault.amount())); - // sol_log(&format!("A self.base.balance: {}", self.base.balance)); - // sol_log("Insufficient base vault reserves"); - // return Err(OreError::InsufficientVaultReserves.into()); - // } - // Ok(()) - // } - - /// Sanity check that quote vault has reserves for all market debts. - /// Assumes the token account has already been validated as the market's quote vault. - pub fn check_quote_vault(&self, quote_vault: &TokenAccount) -> Result<(), OreError> { - if quote_vault.amount() < self.quote.balance + self.fee.uncollected { - sol_log("Insufficient quote vault reserves"); - return Err(OreError::InsufficientVaultReserves.into()); - } - Ok(()) - } -} diff --git a/api/src/state/market/virtual_limit_order.rs b/api/src/state/market/virtual_limit_order.rs deleted file mode 100644 index fab5a2d..0000000 --- a/api/src/state/market/virtual_limit_order.rs +++ /dev/null @@ -1,158 +0,0 @@ -use steel::Clock; - -use crate::{consts::SLOT_WINDOW, error::OreError}; - -use super::{Market, SwapDirection, TokenType, VirtualLimitOrder}; - -impl Market { - /// This function solves the closed-form solution for the size of the virtual limit order - /// in the pool. The virutal limit order is always priced at the snapshot price. - /// - /// The size of the limit order is determined by the following constraint: - /// - /// ```no_run - /// (quote_snapshot / base_snapshot) = (quote_reserves + ∆_quote) / (base_reserves + ∆_base) - /// ``` - /// - /// Note that the signs of ∆_quote and ∆_base are always flipped. - /// - /// This means that the size of the limit order is set such that the new pool price - /// after the swap is equal to the price at the snapshot. - /// - /// Because we know the limit order is priced at the snapshot price, we can derive - /// the following equations: - /// - ∆_base = -∆_quote * base_snapshot / quote_snapshot - /// - ∆_quote = -∆_base * quote_snapshot / base_snapshot - /// - /// - /// We can then solve for ∆_base and ∆_quote after substituting the above equations. There are separate cases - /// for buy and sell - /// - /// - Limit order on the buy side (bid) - /// ```no_run - /// ∆_base = (base_snapshot * quote_reserves - quote_snapshot * base_reserves) / (2 * quote_snapshot) - /// ∆_quote = (base_snapshot * quote_reserves - quote_snapshot * base_reserves) / (2 * base_snapshot) - /// ``` - /// - /// - Limit order on the sell side (ask) - /// ```no_run - /// ∆_base = (quote_snapshot * base_reserves - base_snapshot * quote_reserves) / (2 * quote_snapshot) - /// ∆_quote = (quote_snapshot * base_reserves - base_snapshot * quote_reserves) / (2 * base_snapshot) - /// ``` - pub fn get_virtual_limit_order(&self, direction: SwapDirection) -> VirtualLimitOrder { - // Upcast data. - let base_balance = self.base.liquidity(); - let quote_balance = self.quote.liquidity(); - let base_snapshot = self.snapshot.base_balance as u128; - let quote_snapshot = self.snapshot.quote_balance as u128; - - // Get virtual limit order. - match direction { - SwapDirection::Buy => { - let ask = if quote_snapshot * base_balance > base_snapshot * quote_balance { - let size_in_quote = (quote_snapshot * base_balance - - base_snapshot * quote_balance) - / (2 * base_snapshot); - let size_in_base = size_in_quote * base_snapshot / quote_snapshot; - VirtualLimitOrder { - size_in_base, - size_in_quote, - } - } else { - VirtualLimitOrder::default() - }; - ask - } - SwapDirection::Sell => { - let bid = if base_snapshot * quote_balance > quote_snapshot * base_balance { - let size_in_base = (base_snapshot * quote_balance - - quote_snapshot * base_balance) - / (2 * quote_snapshot); - let size_in_quote = size_in_base * quote_snapshot / base_snapshot; - VirtualLimitOrder { - size_in_base, - size_in_quote, - } - } else { - VirtualLimitOrder::default() - }; - bid - } - } - } - - /// This function returns the size of the virtual limit order in the complementary token type - /// given an `amount` and the `input_token_type`. - /// - If the `input_token_type` is Base, then the size of the limit order in Quote is computed. - /// - If the `input_token_type` is Quote, then the size of the limit order in Base is computed. - pub(crate) fn get_complementary_limit_order_size( - &self, - amount: u128, - direction: SwapDirection, - token_type: TokenType, - ) -> u128 { - if amount == 0 { - return 0; - } - let quote_snapshot = self.snapshot.quote_balance as u128; - let base_snapshot = self.snapshot.base_balance as u128; - - match direction { - SwapDirection::Buy => { - match token_type { - // If `amount` is in base, then the size of the limit order in quote is computed and rounded up - TokenType::Base => ((amount * quote_snapshot).saturating_sub(1) - / base_snapshot) - .saturating_add(1), - // If `amount` is in quote, then the size of the limit order in base is computed - TokenType::Quote => amount * base_snapshot / quote_snapshot, - } - } - SwapDirection::Sell => { - match token_type { - // If `amount` is in base, then the size of the limit order in quote is computed - TokenType::Base => amount * quote_snapshot / base_snapshot, - // If `amount` is in quote, then the size of the limit order in base is computed and rounded up - TokenType::Quote => ((amount * base_snapshot).saturating_sub(1) - / quote_snapshot) - .saturating_add(1), - } - } - } - } - - pub(crate) fn update_snapshot(&mut self, clock: &Clock) { - let slot = clock.slot; - let snapshot_slot = (slot / SLOT_WINDOW) * SLOT_WINDOW; - if snapshot_slot != self.snapshot.slot { - self.snapshot.slot = snapshot_slot; - self.snapshot.base_balance = self.base.liquidity() as u64; - self.snapshot.quote_balance = self.quote.liquidity() as u64; - } - } - - pub(crate) fn update_reserves( - &mut self, - base: u128, - quote: u128, - direction: SwapDirection, - ) -> Result<(), OreError> { - match direction { - SwapDirection::Buy => { - if base > self.base.balance as u128 { - return Err(OreError::InsufficientVaultReserves.into()); - } - self.base.balance -= base as u64; - self.quote.balance += quote as u64; - } - SwapDirection::Sell => { - if quote > self.quote.balance as u128 { - return Err(OreError::InsufficientVaultReserves.into()); - } - self.base.balance += base as u64; - self.quote.balance -= quote as u64; - } - } - Ok(()) - } -} diff --git a/api/src/state/miner.rs b/api/src/state/miner.rs index a63c9ab..8e36b67 100644 --- a/api/src/state/miner.rs +++ b/api/src/state/miner.rs @@ -10,20 +10,14 @@ pub struct Miner { /// The authority of this miner account. pub authority: Pubkey, - /// The ID of the last block this miner mined in. - pub block_id: u64, + /// The miner's committed square in the current round round. + pub commits: [u64; 25], - /// An account authorized to execute actions on behalf of this miner. - pub executor: Pubkey, + /// The amount of ORE this miner can claim. + pub rewards: u64, - /// The amount of hashpower this miner has committed to the current block. - pub hashpower: u64, - - /// A user-supplied seed for random number generation. - pub seed: [u8; 32], - - /// The total amount of hashpower this miner has committed across all blocks. - pub total_hashpower: u64, + /// The ID of the round this miner last played in. + pub round_id: u64, /// The total amount of ORE this miner has mined across all blocks. pub total_rewards: u64, diff --git a/api/src/state/mod.rs b/api/src/state/mod.rs index 7b25496..f64db81 100644 --- a/api/src/state/mod.rs +++ b/api/src/state/mod.rs @@ -1,13 +1,13 @@ -mod block; +mod board; mod config; -mod market; mod miner; +mod square; mod treasury; -pub use block::*; +pub use board::*; pub use config::*; -pub use market::*; pub use miner::*; +pub use square::*; pub use treasury::*; use crate::consts::*; @@ -17,38 +17,40 @@ use steel::*; #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] pub enum OreAccount { - Block = 100, Config = 101, - Market = 102, Miner = 103, Treasury = 104, + + // + Board = 105, + Square = 106, } -pub fn block_pda(id: u64) -> (Pubkey, u8) { - Pubkey::find_program_address(&[BLOCK, &id.to_le_bytes()], &crate::ID) +pub fn board_pda() -> (Pubkey, u8) { + Pubkey::find_program_address(&[BOARD], &crate::ID) } pub fn config_pda() -> (Pubkey, u8) { Pubkey::find_program_address(&[CONFIG], &crate::ID) } -pub fn market_pda() -> (Pubkey, u8) { - Pubkey::find_program_address(&[MARKET], &crate::ID) -} - pub fn miner_pda(authority: Pubkey) -> (Pubkey, u8) { Pubkey::find_program_address(&[MINER, &authority.to_bytes()], &crate::ID) } +pub fn square_pda(id: u64) -> (Pubkey, u8) { + Pubkey::find_program_address(&[SQUARE, &id.to_le_bytes()], &crate::ID) +} + pub fn vault_address() -> Pubkey { - let market_address = market_pda().0; - spl_associated_token_account::get_associated_token_address(&market_address, &MINT_ADDRESS) + let board_address = board_pda().0; + spl_associated_token_account::get_associated_token_address(&board_address, &MINT_ADDRESS) } pub fn treasury_pda() -> (Pubkey, u8) { Pubkey::find_program_address(&[TREASURY], &crate::ID) } -pub fn treasury_tokens_address() -> Pubkey { - spl_associated_token_account::get_associated_token_address(&TREASURY_ADDRESS, &MINT_ADDRESS) -} +// pub fn treasury_tokens_address() -> Pubkey { +// spl_associated_token_account::get_associated_token_address(&TREASURY_ADDRESS, &MINT_ADDRESS) +// } diff --git a/api/src/state/square.rs b/api/src/state/square.rs new file mode 100644 index 0000000..95d37e6 --- /dev/null +++ b/api/src/state/square.rs @@ -0,0 +1,29 @@ +use steel::*; + +use crate::state::square_pda; + +use super::OreAccount; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct Square { + /// The count of miners on this square. + pub count: u64, + + /// The commits for the round. + pub id: u64, + + /// The round number. + pub round_id: u64, + + /// The miners on this square. + pub miners: [Pubkey; 16], +} + +impl Square { + pub fn pda(&self) -> (Pubkey, u8) { + square_pda(self.id) + } +} + +account!(OreAccount, Square); diff --git a/cli/src/main.rs b/cli/src/main.rs index 5244590..11af008 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,7 +1,6 @@ use std::str::FromStr; use ore_api::prelude::*; -use sha3::{Digest, Sha3_256}; use solana_account_decoder::UiAccountEncoding; use solana_client::{ client_error::{reqwest::StatusCode, ClientErrorKind}, @@ -11,13 +10,12 @@ use solana_client::{ }; use solana_sdk::{ compute_budget::ComputeBudgetInstruction, - keccak::hash, pubkey, pubkey::Pubkey, signature::{read_keypair_file, Signer}, + slot_hashes::SlotHashes, transaction::Transaction, }; -use spl_token::amount_to_ui_amount; use steel::{AccountDeserialize, Clock, Discriminator}; #[tokio::main] @@ -38,38 +36,17 @@ async fn main() { "claim" => { claim(&rpc, &payer).await.unwrap(); } - "close" => { - close(&rpc, &payer).await.unwrap(); - } - "close_all" => { - close_all(&rpc, &payer).await.unwrap(); - } - "market" => { - log_market(&rpc).await.unwrap(); - } - "block" => { - log_block(&rpc).await.unwrap(); - } - "blocks" => { - log_blocks(&rpc).await.unwrap(); + "board" => { + log_board(&rpc).await.unwrap(); } "config" => { log_config(&rpc).await.unwrap(); } - "mine" => { - mine(&rpc, &payer).await.unwrap(); - } - "test_mine" => { - test_mine().await.unwrap(); - } "initialize" => { initialize(&rpc, &payer).await.unwrap(); } - "open" => { - open(&rpc, &payer).await.unwrap(); - } - "swap" => { - swap(&rpc, &payer).await.unwrap(); + "initialize_squares" => { + initialize_squares(&rpc, &payer).await.unwrap(); } "reset" => { reset(&rpc, &payer).await.unwrap(); @@ -80,22 +57,41 @@ async fn main() { "miner" => { log_miner(&rpc, &payer).await.unwrap(); } + "prospect" => { + prospect(&rpc, &payer).await.unwrap(); + } + "prospect_some" => { + prospect_some(&rpc, &payer).await.unwrap(); + } + "square" => { + log_square(&rpc).await.unwrap(); + } "set_admin" => { set_admin(&rpc, &payer).await.unwrap(); } "set_fee_collector" => { set_fee_collector(&rpc, &payer).await.unwrap(); } - "benchmark" => { - benchmark_keccak().await.unwrap(); - } "claim_seeker" => { claim_seeker(&rpc, &payer).await.unwrap(); } + "keys" => { + keys().await.unwrap(); + } _ => panic!("Invalid command"), }; } +async fn keys() -> Result<(), anyhow::Error> { + let treasury_address = ore_api::state::treasury_pda().0; + let config_address = ore_api::state::config_pda().0; + println!("Treasury: {}", treasury_address); + println!("Config: {}", config_address); + // let keys = get_program_accounts::(rpc, ore_api::ID, vec![]).await?; + // println!("Keys: {:?}", keys); + Ok(()) +} + async fn initialize( rpc: &RpcClient, payer: &solana_sdk::signer::keypair::Keypair, @@ -105,14 +101,11 @@ async fn initialize( Ok(()) } -async fn close( +async fn initialize_squares( rpc: &RpcClient, payer: &solana_sdk::signer::keypair::Keypair, ) -> Result<(), anyhow::Error> { - let id_str = std::env::var("ID").expect("Missing ID env var"); - let id = id_str.parse::()?; - let block = get_block(rpc, id).await?; - let ix = ore_api::sdk::close(payer.pubkey(), block.opener, block.best_hash_miner, id); + let ix = ore_api::sdk::initialize_squares(payer.pubkey()); submit_transaction(rpc, payer, &[ix]).await?; Ok(()) } @@ -126,142 +119,55 @@ async fn claim( Ok(()) } -async fn close_all( - rpc: &RpcClient, - payer: &solana_sdk::signer::keypair::Keypair, -) -> Result<(), anyhow::Error> { - let clock = get_clock(rpc).await?; - let blocks = get_blocks(rpc).await?; - println!("Closing all blocks... {}", blocks.len()); - for (_, block) in blocks { - println!("Closing block {}", block.id); - if clock.slot > block.end_slot + MINING_WINDOW { - let ix = ore_api::sdk::close( - payer.pubkey(), - block.opener, - block.best_hash_miner, - block.id, - ); - submit_transaction(rpc, payer, &[ix]).await?; - } - } - Ok(()) -} - -async fn test_mine() -> Result<(), anyhow::Error> { - let authority = Pubkey::from_str("pqspJ298ryBjazPAr95J9sULCVpZe3HbZTWkbC1zrkS").unwrap(); - let slot_hash = [ - 14, 121, 1, 5, 83, 45, 216, 218, 6, 33, 58, 94, 210, 71, 56, 234, 151, 186, 182, 93, 202, - 53, 201, 164, 136, 144, 1, 37, 228, 192, 96, 117, - ]; - let noise = [0u8; 32]; - let block_id: u64 = 3816; - - // CLI - let mut best_hash = [u8::MAX; 32]; - let mut best_nonce = 0; - for nonce in 0..99u64 { - let mut seed = [0u8; 112]; - seed[..8].copy_from_slice(&block_id.to_le_bytes()); - seed[8..40].copy_from_slice(&slot_hash); - seed[40..72].copy_from_slice(&authority.to_bytes()); - seed[72..104].copy_from_slice(&noise); - seed[104..].copy_from_slice(&nonce.to_le_bytes()); - let h1 = solana_program::keccak::hash(&seed).to_bytes(); - - if h1 < best_hash { - best_hash = h1; - best_nonce = nonce; - } - } - - println!("Best hash: {:?}", best_hash); - println!("Best nonce: {:?}", best_nonce); - - Ok(()) -} - -async fn mine( - rpc: &RpcClient, - payer: &solana_sdk::signer::keypair::Keypair, -) -> Result<(), anyhow::Error> { - let miner = get_miner(rpc, payer.pubkey()).await?; - let block = get_block(rpc, miner.block_id).await?; - let clock = get_clock(rpc).await?; - if clock.slot < block.end_slot { - return Err(anyhow::anyhow!("Mining window is not yet open.")); - } - if clock.slot >= block.end_slot + MINING_WINDOW { - return Err(anyhow::anyhow!("Mining window is closed.")); - } - let mut best_hash = [u8::MAX; 32]; - let mut best_nonce = 0; - for i in 0..miner.hashpower { - let mut seed = [0u8; 112]; - seed[..8].copy_from_slice(&block.id.to_le_bytes()); - seed[8..40].copy_from_slice(&block.slot_hash); - seed[40..72].copy_from_slice(&miner.authority.to_bytes()); - seed[72..104].copy_from_slice(&miner.seed); - seed[104..].copy_from_slice(&i.to_le_bytes()); - let h = hash(&seed).to_bytes(); - if h < best_hash { - best_hash = h; - best_nonce = i; - } - } - if block.best_hash < best_hash { - return Err(anyhow::anyhow!("A better hash was already found.")); - } - println!("Found best hash: {:?}", best_hash.to_ascii_lowercase()); - let ix = ore_api::sdk::mine(payer.pubkey(), block.id, best_nonce); - submit_transaction(rpc, payer, &[ix]).await?; - Ok(()) -} - async fn reset( rpc: &RpcClient, payer: &solana_sdk::signer::keypair::Keypair, ) -> Result<(), anyhow::Error> { - let market = get_market(rpc).await?; - let id = market.block_id; - let open_ix = ore_api::sdk::open(payer.pubkey(), id + 1); - let reset_ix = ore_api::sdk::reset(payer.pubkey(), id); - submit_transaction(rpc, payer, &[open_ix, reset_ix]).await?; + let board = get_board(rpc).await?; + let slot_hashes = get_slot_hashes(rpc).await?; + let mut miners = vec![]; + if let Some(slot_hash) = slot_hashes.get(&board.end_slot) { + let id = get_winning_square(&slot_hash.to_bytes()); + let square = get_square(rpc, id).await?; + println!("Winning square: {}", id); + println!("Miners: {:?}", square.miners); + miners = square.miners.to_vec(); + }; + let reset_ix = ore_api::sdk::reset(payer.pubkey(), miners); + submit_transaction(rpc, payer, &[reset_ix]).await?; Ok(()) } -async fn open( +async fn prospect( rpc: &RpcClient, payer: &solana_sdk::signer::keypair::Keypair, ) -> Result<(), anyhow::Error> { - let id_str = std::env::var("ID").expect("Missing ID env var"); - let id = id_str.parse::()?; - let ix = ore_api::sdk::open(payer.pubkey(), id); - submit_transaction(rpc, payer, &[ix]).await?; - Ok(()) -} - -async fn swap( - rpc: &RpcClient, - payer: &solana_sdk::signer::keypair::Keypair, -) -> Result<(), anyhow::Error> { - let market = get_market(rpc).await?; - let id = market.block_id; + let amount = std::env::var("AMOUNT").expect("Missing AMOUNT env var"); + let amount = u64::from_str(&amount).expect("Invalid AMOUNT"); + let square_id = std::env::var("SQUARE").expect("Missing SQUARE env var"); + let square_id = u64::from_str(&square_id).expect("Invalid SQUARE"); let config = get_config(rpc).await?; - let fee_collector = config.fee_collector; - let ix = ore_api::sdk::swap( - payer.pubkey(), - id, - fee_collector, - 1_000_000_000, - SwapDirection::Buy, - SwapPrecision::ExactIn, - [0; 32], - ); + let ix = ore_api::sdk::prospect(payer.pubkey(), config.fee_collector, amount, square_id); submit_transaction(rpc, payer, &[ix]).await?; Ok(()) } +async fn prospect_some( + rpc: &RpcClient, + payer: &solana_sdk::signer::keypair::Keypair, +) -> Result<(), anyhow::Error> { + let amount = std::env::var("AMOUNT").expect("Missing AMOUNT env var"); + let amount = u64::from_str(&amount).expect("Invalid AMOUNT"); + let config = get_config(rpc).await?; + let mut ixs = vec![]; + for i in 0..8 { + let ix = ore_api::sdk::prospect(payer.pubkey(), config.fee_collector, amount, i as u64); + ixs.push(ix); + } + submit_transaction(rpc, payer, &ixs).await?; + Ok(()) +} + async fn claim_seeker( rpc: &RpcClient, payer: &solana_sdk::signer::keypair::Keypair, @@ -300,6 +206,18 @@ async fn log_treasury(_rpc: &RpcClient) -> Result<(), anyhow::Error> { Ok(()) } +async fn log_square(rpc: &RpcClient) -> Result<(), anyhow::Error> { + let id = std::env::var("ID").expect("Missing ID env var"); + let id = u64::from_str(&id).expect("Invalid ID"); + let square = get_square(rpc, id).await?; + println!("Square"); + println!(" id: {}", square.id); + println!(" count: {}", square.count); + println!(" round_id: {}", square.round_id); + println!(" miners: {:?}", square.miners); + Ok(()) +} + async fn log_miner( rpc: &RpcClient, payer: &solana_sdk::signer::keypair::Keypair, @@ -311,10 +229,9 @@ async fn log_miner( println!("Miner"); println!(" address: {}", miner_address); println!(" authority: {}", authority); - println!(" block_id: {}", miner.block_id); - println!(" hashpower: {}", miner.hashpower); - println!(" seed: {:?}", miner.seed); - println!(" total_hashpower: {}", miner.total_hashpower); + println!(" commits: {:?}", miner.commits); + println!(" rewards: {}", miner.rewards); + println!(" round_id: {}", miner.round_id); println!(" total_rewards: {}", miner.total_rewards); Ok(()) } @@ -341,69 +258,52 @@ async fn log_config(rpc: &RpcClient) -> Result<(), anyhow::Error> { Ok(()) } -async fn log_market(rpc: &RpcClient) -> Result<(), anyhow::Error> { - let market = get_market(&rpc).await?; - let block = get_block(&rpc, market.block_id).await?; - let clock = get_clock(rpc).await?; - print_market(market); - println!(""); - print_block(block, &clock); - Ok(()) -} - -async fn log_block(rpc: &RpcClient) -> Result<(), anyhow::Error> { - let id_str = std::env::var("ID").expect("Missing ID env var"); - let id = id_str.parse::()?; - let block = get_block(&rpc, id).await?; +async fn log_board(rpc: &RpcClient) -> Result<(), anyhow::Error> { + let board = get_board(&rpc).await?; let clock = get_clock(&rpc).await?; - print_block(block, &clock); + print_board(board, &clock); Ok(()) } -fn print_block(block: Block, clock: &Clock) { +fn print_board(board: Board, clock: &Clock) { let current_slot = clock.slot; - println!("Block"); - println!(" Id: {:?}", block.id); - println!(" Slot hash: {:?}", block.slot_hash); - println!(" Total hashpower: {}", block.total_hashpower); - println!(" Best hash: {:?}", block.best_hash); - println!(" Best hash miner: {:?}", block.best_hash_miner); - println!(" Start slot: {}", block.start_slot); - println!(" End slot: {}", block.end_slot); - println!(" Reward: {}", block.reward); + println!("Board"); + println!(" Id: {:?}", board.id); + println!(" Slot hash: {:?}", board.slot_hash); + println!(" Total commits: {}", board.total_commits); + println!(" Total burned: {}", board.total_burned); + println!(" Start slot: {}", board.start_slot); + println!(" End slot: {}", board.end_slot); + println!(" Commits: {:?}", board.commits); + if board.slot_hash != [0; 32] { + println!(" Winning square: {}", get_winning_square(&board.slot_hash)); + } println!( " Time remaining: {} sec", - (block.end_slot.saturating_sub(current_slot) as f64) * 0.4 + (board.end_slot.saturating_sub(current_slot) as f64) * 0.4 ); } -fn print_market(market: Market) { - println!("Market"); - println!(" Block id: {}", market.block_id); - println!(" Base token: {:?}", market.base); - println!(" Quote token: {:?}", market.quote); - println!(" Fee: {:?}", market.fee); - println!(" Snapshot: {:?}", market.snapshot); - let price = amount_to_ui_amount(market.quote.liquidity() as u64, TOKEN_DECIMALS) - / market.base.liquidity() as f64; - println!(" Price: {:.11?} ORE / hash", price); +async fn get_board(rpc: &RpcClient) -> Result { + let board_pda = ore_api::state::board_pda(); + let account = rpc.get_account(&board_pda.0).await?; + let board = Board::try_from_bytes(&account.data)?; + Ok(*board) } -async fn log_blocks(rpc: &RpcClient) -> Result<(), anyhow::Error> { - let clock = get_clock(&rpc).await?; - let mut blocks = get_blocks(&rpc).await?; - blocks.sort_by_key(|(_, block)| block.id); - for (_, block) in blocks { - print_block(block, &clock); - } - Ok(()) +async fn get_slot_hashes(rpc: &RpcClient) -> Result { + let data = rpc + .get_account_data(&solana_sdk::sysvar::slot_hashes::ID) + .await?; + let slot_hashes = bincode::deserialize::(&data)?; + Ok(slot_hashes) } -async fn get_block(rpc: &RpcClient, id: u64) -> Result { - let block_pda = ore_api::state::block_pda(id); - let account = rpc.get_account(&block_pda.0).await?; - let block = Block::try_from_bytes(&account.data)?; - Ok(*block) +async fn get_square(rpc: &RpcClient, id: u64) -> Result { + let square_pda = ore_api::state::square_pda(id); + let account = rpc.get_account(&square_pda.0).await?; + let square = Square::try_from_bytes(&account.data)?; + Ok(*square) } async fn get_config(rpc: &RpcClient) -> Result { @@ -413,13 +313,6 @@ async fn get_config(rpc: &RpcClient) -> Result { Ok(*config) } -async fn get_market(rpc: &RpcClient) -> Result { - let market_pda = ore_api::state::market_pda(); - let account = rpc.get_account(&market_pda.0).await?; - let market = Market::try_from_bytes(&account.data)?; - Ok(*market) -} - async fn get_miner(rpc: &RpcClient, authority: Pubkey) -> Result { let miner_pda = ore_api::state::miner_pda(authority); let account = rpc.get_account(&miner_pda.0).await?; @@ -433,16 +326,23 @@ async fn get_clock(rpc: &RpcClient) -> Result { Ok(clock) } -async fn get_blocks(rpc: &RpcClient) -> Result, anyhow::Error> { - let blocks = get_program_accounts::(rpc, ore_api::ID, vec![]).await?; - Ok(blocks) -} - async fn get_miners(rpc: &RpcClient) -> Result, anyhow::Error> { let miners = get_program_accounts::(rpc, ore_api::ID, vec![]).await?; Ok(miners) } +fn get_winning_square(slot_hash: &[u8]) -> u64 { + // Use slot hash to generate a random u64 + let r1 = u64::from_le_bytes(slot_hash[0..8].try_into().unwrap()); + let r2 = u64::from_le_bytes(slot_hash[8..16].try_into().unwrap()); + let r3 = u64::from_le_bytes(slot_hash[16..24].try_into().unwrap()); + let r4 = u64::from_le_bytes(slot_hash[24..32].try_into().unwrap()); + let r = r1 ^ r2 ^ r3 ^ r4; + + // Returns a value in the range [0, 24] inclusive + r % 25 +} + #[allow(dead_code)] async fn simulate_transaction( rpc: &RpcClient, @@ -548,25 +448,3 @@ where }, } } - -async fn benchmark_keccak() -> Result<(), anyhow::Error> { - use solana_program::keccak::hash; - use std::time::Instant; - - const NUM_HASHES: u64 = 1_000_000; - let start = Instant::now(); - - for i in 0..NUM_HASHES { - let _ = hash(&i.to_le_bytes()); - } - - let duration = start.elapsed(); - let hashes_per_sec = NUM_HASHES as f64 / duration.as_secs_f64(); - - println!("\nKeccak-256 Benchmark:"); - println!("Time elapsed: {:.2?}", duration); - println!("Hashes computed: {}", NUM_HASHES); - println!("Hashes per second: {:.0}", hashes_per_sec); - - Ok(()) -} diff --git a/localnet.sh b/localnet.sh index 76b14b9..4544114 100755 --- a/localnet.sh +++ b/localnet.sh @@ -1,4 +1 @@ -solana-test-validator \ - -r \ - --bpf-program EmxGq9Bj8q6V998KDq3v19ch2DnARwhcNL2uXtgDFbra target/deploy/ore.so \ - --url https://api.mainnet-beta.solana.com +solana-test-validator -r --url https://api.mainnet-beta.solana.com --bpf-program oreV3EG1i9BEgiAJ8b177Z2S2rMarzak4NMv1kULvWv target/deploy/ore.so --clone oreoU2P8bN6jkk3jbaiVxYnG1dCXcYxwhwyK9jSybcp --clone 45db2FSR4mcXdSVVZbKbwojU6uYDpMyhpEi7cC8nHaWG --clone 9c9X7aDRAF41faiDs94ELjT19UrGnn72wBW9hPsS4Awy --clone HBUh9g46wk2X89CvaNN15UmsznP59rh6od1h8JwYAopk --clone Gce36ZUsBDJsoLrfCBxUB5Sfq2DsGunofStvxFx6rBiD --clone 3Ag5aesdDawsCWP32YqZN2NB2eUoH5e8UKcgP9j4arFX \ No newline at end of file diff --git a/program/src/claim.rs b/program/src/claim.rs index 23d4036..4a334d6 100644 --- a/program/src/claim.rs +++ b/program/src/claim.rs @@ -8,18 +8,19 @@ pub fn process_claim(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult let amount = u64::from_le_bytes(args.amount); // Load accounts. - let [signer_info, miner_info, miner_tokens_info, recipient_info, mint_info, system_program, token_program, associated_token_program] = + let [signer_info, board_info, miner_info, mint_info, recipient_info, vault_info, system_program, token_program, associated_token_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; signer_info.is_signer()?; + board_info.as_account::(&ore_api::ID)?; let miner = miner_info - .as_account::(&ore_api::ID)? - .assert(|m| m.authority == *signer_info.key)?; - let miner_tokens = - miner_tokens_info.as_associated_token_account(&miner_info.key, &mint_info.key)?; + .as_account_mut::(&ore_api::ID)? + .assert_mut(|m| m.authority == *signer_info.key)?; mint_info.has_address(&MINT_ADDRESS)?.as_mint()?; + recipient_info.as_associated_token_account(&signer_info.key, &mint_info.key)?; + vault_info.as_associated_token_account(&board_info.key, &mint_info.key)?; system_program.is_program(&system_program::ID)?; token_program.is_program(&spl_token::ID)?; associated_token_program.is_program(&spl_associated_token_account::ID)?; @@ -40,16 +41,19 @@ pub fn process_claim(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult } // Load amount. - let amount = miner_tokens.amount().min(amount); + let amount = miner.rewards.min(amount); + + // Update miner. + miner.rewards -= amount; // Transfer reward to recipient. transfer_signed( - miner_info, - miner_tokens_info, + board_info, + vault_info, recipient_info, token_program, amount, - &[MINER, miner.authority.as_ref()], + &[BOARD], )?; Ok(()) diff --git a/program/src/close.rs b/program/src/close.rs deleted file mode 100644 index 4844200..0000000 --- a/program/src/close.rs +++ /dev/null @@ -1,80 +0,0 @@ -use ore_api::prelude::*; -use steel::*; - -/// Closes a block. -pub fn process_close(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { - // Load accounts. - let clock = Clock::get()?; - let [signer_info, block_info, miner_info, market_info, miner_tokens_info, mint_info, opener_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - signer_info.is_signer()?; - let block = block_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|b| clock.slot >= b.end_slot + MINING_WINDOW)?; // Window for submitting hashes has closed - market_info - .as_account::(&ore_api::ID)? - .assert(|m| m.block_id > block.id)?; - mint_info.has_address(&MINT_ADDRESS)?.as_mint()?; - opener_info.is_writable()?.has_address(&block.opener)?; - treasury_info.as_account::(&ore_api::ID)?; - treasury_tokens_info - .is_writable()? - .as_associated_token_account(treasury_info.key, mint_info.key)?; - system_program.is_program(&system_program::ID)?; - token_program.is_program(&spl_token::ID)?; - associated_token_program.is_program(&spl_associated_token_account::ID)?; - - // Load miner rewards. - if miner_tokens_info.data_is_empty() { - create_associated_token_account( - signer_info, - miner_info, - miner_tokens_info, - mint_info, - system_program, - token_program, - associated_token_program, - )?; - } else { - miner_tokens_info.as_associated_token_account(&miner_info.key, &mint_info.key)?; - } - - // Payout block reward to winning miner. - if block.best_hash_miner != Pubkey::default() { - // Load winning miner. - let miner = miner_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|m| m.authority == block.best_hash_miner)?; - - // Update stats. - miner.total_rewards += block.reward; - - // Transfer reward to miner. - transfer_signed( - treasury_info, - treasury_tokens_info, - miner_tokens_info, - token_program, - block.reward, - &[TREASURY], - )?; - } else { - // If no one won, burn the block reward. - burn_signed( - treasury_tokens_info, - mint_info, - treasury_info, - token_program, - block.reward, - &[TREASURY], - )?; - } - - // Close block. - block_info.close(opener_info)?; - - Ok(()) -} diff --git a/program/src/initialize.rs b/program/src/initialize.rs index ded3d47..5eac895 100644 --- a/program/src/initialize.rs +++ b/program/src/initialize.rs @@ -4,22 +4,43 @@ use steel::*; /// Initializes the program. pub fn process_initialize(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { // Load accounts. - let [signer_info, config_info, market_info, mint_info, treasury_info, treasury_tokens_info, vault_info, system_program, token_program, associated_token_program] = + let [signer_info, board_info, config_info, mint_info, treasury_info, vault_info, system_program, token_program, associated_token_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; signer_info.is_signer()?.has_address(&ADMIN_ADDRESS)?; + board_info.has_seeds(&[BOARD], &ore_api::ID)?; config_info.has_seeds(&[CONFIG], &ore_api::ID)?; - market_info.has_seeds(&[MARKET], &ore_api::ID)?; mint_info.has_address(&MINT_ADDRESS)?.as_mint()?; treasury_info.has_seeds(&[TREASURY], &ore_api::ID)?; - treasury_tokens_info.is_writable()?; vault_info.has_address(&vault_address())?; system_program.is_program(&system_program::ID)?; token_program.is_program(&spl_token::ID)?; associated_token_program.is_program(&spl_associated_token_account::ID)?; + // Create board account. + if board_info.data_is_empty() { + create_program_account::( + board_info, + system_program, + signer_info, + &ore_api::ID, + &[BOARD], + )?; + let board = board_info.as_account_mut::(&ore_api::ID)?; + board.commits = [0; 25]; + board.id = 0; + board.start_at = 0; + board.start_slot = 0; + board.end_slot = 0; + board.slot_hash = [0; 32]; + board.total_commits = 0; + board.total_burned = 0; + } else { + board_info.as_account::(&ore_api::ID)?; + } + // Create config account. if config_info.data_is_empty() { create_program_account::( @@ -39,42 +60,6 @@ pub fn process_initialize(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program config_info.as_account::(&ore_api::ID)?; } - // Initialize market. - if market_info.data_is_empty() { - create_program_account::( - market_info, - system_program, - signer_info, - &ore_api::ID, - &[MARKET], - )?; - let market = market_info.as_account_mut::(&ore_api::ID)?; - market.base = TokenParams { - mint: Pubkey::default(), // Virtual token - balance: 0, - balance_virtual: 0, - }; - market.quote = TokenParams { - mint: MINT_ADDRESS, - balance: 0, - balance_virtual: 0, - }; - market.fee = FeeParams { - rate: 0, - uncollected: 0, - cumulative: 0, - }; - market.snapshot = Snapshot { - enabled: 1, - base_balance: 0, - quote_balance: 0, - slot: 0, - }; - market.block_id = 0; - } else { - market_info.as_account::(&ore_api::ID)?; - } - // Create treasury account. if treasury_info.data_is_empty() { create_program_account::( @@ -88,26 +73,11 @@ pub fn process_initialize(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program treasury_info.as_account::(&ore_api::ID)?; } - // Load treasury tokens. - if treasury_tokens_info.data_is_empty() { - create_associated_token_account( - signer_info, - treasury_info, - treasury_tokens_info, - mint_info, - system_program, - token_program, - associated_token_program, - )?; - } else { - treasury_tokens_info.as_associated_token_account(treasury_info.key, mint_info.key)?; - } - // Initialize vault token account. if vault_info.data_is_empty() { create_associated_token_account( signer_info, - market_info, + board_info, vault_info, mint_info, system_program, @@ -115,7 +85,7 @@ pub fn process_initialize(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program associated_token_program, )?; } else { - vault_info.as_associated_token_account(market_info.key, mint_info.key)?; + vault_info.as_associated_token_account(board_info.key, mint_info.key)?; } Ok(()) diff --git a/program/src/initialize_square.rs b/program/src/initialize_square.rs new file mode 100644 index 0000000..39e2f4b --- /dev/null +++ b/program/src/initialize_square.rs @@ -0,0 +1,37 @@ +use ore_api::prelude::*; +use steel::*; + +/// Initializes the program. +pub fn process_initialize_square(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { + // Load accounts. + let (required_accounts, square_accounts) = accounts.split_at(2); + let [signer_info, system_program] = required_accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + signer_info.is_signer()?.has_address(&ADMIN_ADDRESS)?; + system_program.is_program(&system_program::ID)?; + + // Create config account. + for i in 0..25 { + let square_info = &square_accounts[i]; + if square_info.data_is_empty() { + create_program_account::( + square_info, + system_program, + signer_info, + &ore_api::ID, + &[SQUARE, &(i as u64).to_le_bytes()], + )?; + let square = square_info.as_account_mut::(&ore_api::ID)?; + square.id = i as u64; + square.round_id = 0; + square.miners = [Pubkey::default(); 16]; + } else { + square_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|s| s.id == i as u64)?; + } + } + + Ok(()) +} diff --git a/program/src/lib.rs b/program/src/lib.rs index db0e51e..fe81087 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -1,33 +1,21 @@ mod claim; mod claim_seeker; -mod close; mod initialize; -mod log; -mod mine; -mod open; +mod initialize_square; +mod prospect; mod reset; mod set_admin; -mod set_block_duration; mod set_fee_collector; -mod set_fee_rate; -mod set_sniper_fee_duration; -mod swap; mod whitelist; use claim::*; use claim_seeker::*; -use close::*; use initialize::*; -use log::*; -use mine::*; -use open::*; +use initialize_square::*; +use prospect::*; use reset::*; use set_admin::*; -use set_block_duration::*; use set_fee_collector::*; -use set_fee_rate::*; -use set_sniper_fee_duration::*; -use swap::*; use ore_api::instruction::*; use steel::*; @@ -42,23 +30,19 @@ pub fn process_instruction( match ix { // User OreInstruction::Claim => process_claim(accounts, data)?, - OreInstruction::Open => process_open(accounts, data)?, - OreInstruction::Close => process_close(accounts, data)?, - OreInstruction::Log => process_log(accounts, data)?, - OreInstruction::Mine => process_mine(accounts, data)?, - OreInstruction::Swap => process_swap(accounts, data)?, - OreInstruction::Reset => process_reset(accounts, data)?, OreInstruction::Initialize => process_initialize(accounts, data)?, + OreInstruction::InitializeSquare => process_initialize_square(accounts, data)?, + OreInstruction::Prospect => process_prospect(accounts, data)?, + OreInstruction::Reset => process_reset(accounts, data)?, // Admin OreInstruction::SetAdmin => process_set_admin(accounts, data)?, - OreInstruction::SetBlockDuration => process_set_block_duration(accounts, data)?, OreInstruction::SetFeeCollector => process_set_fee_collector(accounts, data)?, - OreInstruction::SetFeeRate => process_set_fee_rate(accounts, data)?, - OreInstruction::SetSniperFeeDuration => process_set_sniper_fee_duration(accounts, data)?, // Seeker OreInstruction::ClaimSeeker => process_claim_seeker(accounts, data)?, + + _ => return Err(ProgramError::InvalidInstructionData), } Ok(()) diff --git a/program/src/log.rs b/program/src/log.rs deleted file mode 100644 index f6f11db..0000000 --- a/program/src/log.rs +++ /dev/null @@ -1,17 +0,0 @@ -use ore_api::prelude::*; -use steel::*; - -/// No-op, use instruction data for logging w/o truncation. -pub fn process_log(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { - // Load accounts. - let [signer_info] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - signer_info - .is_signer()? - .as_account::(&ore_api::ID)?; - - // For data integrity, only the market can log messages. - - Ok(()) -} diff --git a/program/src/mine.rs b/program/src/mine.rs deleted file mode 100644 index 16627fb..0000000 --- a/program/src/mine.rs +++ /dev/null @@ -1,81 +0,0 @@ -use ore_api::prelude::*; -use solana_nostd_keccak::hash; -use solana_program::log::sol_log; -use steel::*; - -use crate::whitelist::AUTHORIZED_ACCOUNTS; - -/// Mine a block. -pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { - // Parse data. - let args = Mine::try_from_bytes(data)?; - let nonce = u64::from_le_bytes(args.nonce); - - // Load accounts. - let clock = Clock::get()?; - let [signer_info, block_info, market_info, miner_info, ore_program] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - signer_info.is_signer()?; - let block = block_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|b| clock.slot >= b.end_slot)? // Block has stopped trading - .assert_mut(|b| clock.slot < b.end_slot + MINING_WINDOW)? // Give 1500 slots to submit hashes - .assert_mut(|b| b.slot_hash != [0; 32])?; // Slot hash is set - market_info.as_account::(&ore_api::ID)?; - let miner = miner_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|m| m.authority == *signer_info.key || m.executor == *signer_info.key)? // Account belongs to authority - .assert_mut(|m| m.block_id == block.id)? // Only allow miner to submit hashes for their current block - .assert_mut(|m| m.hashpower > nonce)?; // Only allow miner to submit nonces for their hashpower range - ore_program.is_program(&ore_api::ID)?; - - // Check if the signer is authorized. - if !AUTHORIZED_ACCOUNTS.contains(signer_info.key) { - return Err(ProgramError::InvalidAccountData); - } - - sol_log(&format!("Authorized account: {}", signer_info.key)); - - // Generate secure hash with provided nonce. - let mut seed = [0u8; 112]; - seed[..8].copy_from_slice(&block.id.to_le_bytes()); - seed[8..40].copy_from_slice(&block.slot_hash); - seed[40..72].copy_from_slice(&miner.authority.to_bytes()); - seed[72..104].copy_from_slice(&miner.seed); - seed[104..].copy_from_slice(&nonce.to_le_bytes()); - let h = hash(&seed); - - sol_log(&format!("Slot hash: {:?}", block.slot_hash)); - sol_log(&format!("Authority: {:?}", miner.authority)); - sol_log(&format!("Nonce: {:?}", nonce)); - sol_log(&format!("Seed: {:?}", miner.seed)); - sol_log(&format!("Hash: {:?}", h)); - - // If hash is best hash, update best hash. - if h < block.best_hash { - block.best_hash = h; - block.best_hash_miner = miner.authority; - } - - // Emit event. - - program_log( - &[market_info.clone(), ore_program.clone()], - &MineEvent { - disc: 2, - authority: *signer_info.key, - block_id: block.id, - nonce, - hashpower: miner.hashpower, - is_best: (block.best_hash_miner == miner.authority) as u64, - ts: clock.unix_timestamp, - } - .to_bytes(), - )?; - - // Only allow miners to submit 1 hash per block. - miner.hashpower = 0; - - Ok(()) -} diff --git a/program/src/open.rs b/program/src/open.rs deleted file mode 100644 index 01d5229..0000000 --- a/program/src/open.rs +++ /dev/null @@ -1,52 +0,0 @@ -use ore_api::prelude::*; -use steel::*; - -/// Opens a new block. -pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { - // Parse data. - let args = Open::try_from_bytes(data)?; - let id = u64::from_le_bytes(args.id); - - // Load accounts. - let [signer_info, block_info, market_info, system_program] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - signer_info.is_signer()?; - market_info - .as_account::(&ore_api::ID)? - .assert(|m| m.block_id < id)?; // Only allow opening blocks in forward bias - system_program.is_program(&system_program::ID)?; - - // Create block, if it doesn't exist. - if block_info.data_is_empty() { - block_info - .is_empty()? // Account has not been initialized - .is_writable()? // Account is writable - .has_seeds(&[BLOCK, &id.to_le_bytes()], &ore_api::ID)?; // Account has correct seeds - - // Create block account. - create_program_account::( - block_info, - system_program, - signer_info, - &ore_api::ID, - &[BLOCK, &id.to_le_bytes()], - )?; - let block = block_info.as_account_mut::(&ore_api::ID)?; - block.id = id; - block.opener = *signer_info.key; - block.best_hash = [u8::MAX; 32]; - block.best_hash_miner = Pubkey::default(); - block.reward = 0; // Set by reset instruction - block.start_slot = u64::MAX; // Set by reset instruction - block.end_slot = u64::MAX; // Set by reset instruction - block.slot_hash = [0; 32]; // Set by reset instruction - block.total_hashpower = 0; - } else { - block_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|b| b.id == id)?; - } - - Ok(()) -} diff --git a/program/src/prospect.rs b/program/src/prospect.rs new file mode 100644 index 0000000..04ab0fe --- /dev/null +++ b/program/src/prospect.rs @@ -0,0 +1,107 @@ +use ore_api::prelude::*; +use steel::*; + +/// Claims a block reward. +pub fn process_prospect(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { + // Parse data. + let args = Prospect::try_from_bytes(data)?; + let amount = u64::from_le_bytes(args.amount); + let square_id = u64::from_le_bytes(args.square_id); + + // Load accounts. + let clock = Clock::get()?; + let [signer_info, board_info, config_info, fee_collector_info, miner_info, sender_info, square_info, vault_info, system_program, token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + signer_info.is_signer()?; + let board = board_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| { + (clock.slot >= b.start_slot && clock.slot < b.end_slot && b.slot_hash == [0; 32]) + || (clock.slot >= b.end_slot + 25 && b.slot_hash != [0; 32]) + })?; + let config = config_info.as_account::(&ore_api::ID)?; + fee_collector_info + .has_address(&config.fee_collector)? + .is_writable()?; + miner_info.is_writable()?; + sender_info.as_associated_token_account(&signer_info.key, &MINT_ADDRESS)?; + let square = square_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|s| s.id == square_id)?; + vault_info.has_address(&vault_address())?; + system_program.is_program(&system_program::ID)?; + token_program.is_program(&spl_token::ID)?; + + // Create miner. + let miner = if miner_info.data_is_empty() { + create_program_account::( + miner_info, + system_program, + signer_info, + &ore_api::ID, + &[MINER, &signer_info.key.to_bytes()], + )?; + let miner = miner_info.as_account_mut::(&ore_api::ID)?; + miner.authority = *signer_info.key; + miner.commits = [0; 25]; + miner.rewards = 0; + miner.round_id = board.id; + miner.total_rewards = 0; + miner + } else { + miner_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|m| m.authority == *signer_info.key)? + }; + + // Reset board. + if board.slot_hash != [0; 32] { + board.commits = [0; 25]; + board.id += 1; + board.slot_hash = [0; 32]; + board.start_slot = clock.slot; + board.end_slot = clock.slot + 150; // one minute + board.total_commits = 0; + board.total_burned = 0; + } + + // Reset miner + if miner.round_id != board.id { + miner.commits = [0; 25]; + miner.round_id = board.id; + } + + // Reset square + if square.round_id != board.id { + square.count = 0; + square.miners = [Pubkey::default(); 16]; + square.round_id = board.id; + } + + // Update miner + let is_first_play_on_square = miner.commits[square_id as usize] == 0; + miner.commits[square_id as usize] += amount; + + // Update square + if is_first_play_on_square { + square.miners[square.count as usize] = *signer_info.key; + square.count += 1; + } + + // Update board + board.commits[square_id as usize] += amount; + board.total_commits += amount; + + // Transfer tokens. + transfer(signer_info, sender_info, vault_info, token_program, amount)?; + + // Pay fee. + if config.fee_rate > 0 { + fee_collector_info.collect(config.fee_rate, &signer_info)?; + } + + Ok(()) +} diff --git a/program/src/reset.rs b/program/src/reset.rs index 2674393..f0dc2c5 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -1,181 +1,146 @@ use ore_api::prelude::*; -use solana_program::{log::sol_log, slot_hashes::SlotHashes}; +use solana_program::slot_hashes::SlotHashes; use steel::*; -use crate::swap::update_block_reward; - -/// Resets a block. +/// Claims a block reward. pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { // Load accounts. let clock = Clock::get()?; - let [signer_info, block_prev_info, block_next_info, config_info, market_info, mint_info, reserve_tokens_info, treasury_info, treasury_tokens_info, vault_info, system_program, token_program, ore_program, slot_hashes_sysvar] = - accounts + let (required_accounts, miner_accounts) = accounts.split_at(9); + let [signer_info, board_info, mint_info, treasury_info, reserve_tokens_info, vault_info, system_program, token_program, slot_hashes_sysvar] = + required_accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; signer_info.is_signer()?; - let block_next = block_next_info.as_account_mut::(&ore_api::ID)?; - let config = config_info.as_account::(&ore_api::ID)?; - let market = market_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|m| m.block_id == block_next.id - 1)?; - let ore_mint = mint_info.has_address(&MINT_ADDRESS)?.as_mint()?; + let board = board_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| b.slot_hash == [0; 32])? + .assert_mut(|b| clock.slot >= b.end_slot)?; + let mint = mint_info.has_address(&MINT_ADDRESS)?.as_mint()?; reserve_tokens_info .has_address(&BOOST_RESERVE_TOKEN)? .as_token_account()? .assert(|t| t.mint() == MINT_ADDRESS)?; - treasury_info.as_account::(&ore_api::ID)?; - treasury_tokens_info - .is_writable()? - .as_associated_token_account(treasury_info.key, mint_info.key)?; - let vault = vault_info.as_associated_token_account(&market_info.key, &mint_info.key)?; + vault_info.has_address(&vault_address())?; + treasury_info.has_address(&TREASURY_ADDRESS)?; system_program.is_program(&system_program::ID)?; token_program.is_program(&spl_token::ID)?; - ore_program.is_program(&ore_api::ID)?; slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?; - // Load previous block if market block ID is not 0. - if market.block_id > 0 { - let block_prev = block_prev_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|b| b.id == market.block_id)? - .assert_mut(|b| b.end_slot <= clock.slot)?; - - // Get the slot hash, given the end slot of the previous block. - let slot_hashes = - bincode::deserialize::(slot_hashes_sysvar.data.borrow().as_ref()).unwrap(); - if let Some(slot_hash) = slot_hashes.get(&block_prev.end_slot) { - let slot_hash = slot_hash.to_bytes(); - - // Set the block slot hash. - block_prev.slot_hash = slot_hash; - - // Update the block reward. - let clock = Clock::get()?; - let reward_bytes = block_prev.reward.to_le_bytes(); - let limit = reward_bytes[0]; - let steps = reward_bytes[1]; - let (limit, _) = update_block_reward( - limit as u64, - steps as u64, - &slot_hashes, - block_prev.start_slot, - clock.slot, - block_prev.end_slot, - ); - - // Calculate the final block reward. - let block_reward = finalize_block_reward(&block_prev.slot_hash, limit as u64); - - // Limit the block reward to supply cap. - let max_reward = MAX_SUPPLY.saturating_sub(ore_mint.supply()); - let block_reward = block_reward.min(max_reward); - - // Set the block reward. - block_prev.reward = block_reward; - - // Mint the block reward to the treasury. - // This will get transferred to the miner account for claiming when the block is closed. - mint_to_signed( - mint_info, - treasury_tokens_info, - treasury_info, - token_program, - block_reward, - &[TREASURY], - )?; - - // Burn any remaining ORE in market liquidity vault. - let burn_amount = vault.amount(); - burn_signed( - vault_info, - mint_info, - market_info, - token_program, - burn_amount, - &[MARKET], - )?; - - sol_log(&format!("Burn amount: {:?}", burn_amount)); - sol_log(&format!("Mint amount: {:?}", block_reward)); - } - } - - // Burn any remaining ORE in market liquidity vault. - let vault = vault_info.as_associated_token_account(&market_info.key, &mint_info.key)?; - let burn_amount = vault.amount(); - burn_signed( - vault_info, - mint_info, - market_info, - token_program, - burn_amount, - &[MARKET], - )?; - - // Reset market account. - market.block_id = block_next.id; - market.base.balance = HASHPOWER_LIQUIDITY; - market.base.balance_virtual = 0; - market.quote.balance = 0; - market.quote.balance_virtual = ORE_LIQUIDITY; - market.snapshot.enabled = 1; - market.snapshot.base_balance = 0; - market.snapshot.quote_balance = 0; - market.snapshot.slot = 0; - market.fee.rate = 0; - market.fee.uncollected = 0; - market.fee.cumulative = 0; - - // Setup the next block start and end slots. - block_next.start_at = clock.unix_timestamp; - block_next.start_slot = clock.slot; - block_next.end_slot = clock.slot + config.block_duration; - block_next.reward = u64::from_le_bytes([5, 0, 0, 0, 0, 0, 0, 0]); - // Mint tokens to the boost reserve. mint_to_signed( mint_info, reserve_tokens_info, treasury_info, token_program, - ONE_ORE * 3, + ONE_ORE / 3, &[TREASURY], )?; - // Emit event. - program_log( - &[market_info.clone(), ore_program.clone()], - &ResetEvent { - disc: 0, - authority: *signer_info.key, - block_id: block_next.id, - start_slot: block_next.start_slot, - end_slot: block_next.end_slot, - ts: clock.unix_timestamp, + // Sample slot hashes. + let (winning_square, square_commits) = + if let Ok(slot_hash) = get_slot_hash(board.end_slot, slot_hashes_sysvar) { + board.slot_hash = slot_hash; + let winning_square = get_winning_square(&slot_hash); + let square_commits = board.commits[winning_square as usize]; + (winning_square, square_commits) + } else { + // Cannot get slot hash. No one wins. + board.slot_hash = [u8::MAX; 32]; + (u64::MAX, 0) + }; + + // No one won. Burn all rewards. + if square_commits == 0 { + board.total_burned = board.total_commits; + burn_signed( + vault_info, + mint_info, + board_info, + token_program, + board.total_commits, + &[BOARD], + )?; + return Ok(()); + } + + // Get winnings amount (prospects on all non-winning squares). + let mut winnings = 0; + for (i, commits) in board.commits.iter().enumerate() { + if i as u64 != winning_square { + winnings += commits; } - .to_bytes(), + } + + // Get burn amount. + let burn_amount = winnings / 10; // Burn 10% of non-winning prospects. + board.total_burned = burn_amount; + let winnings = winnings - burn_amount; + burn_signed( + vault_info, + mint_info, + board_info, + token_program, + burn_amount, + &[BOARD], )?; + // Mint 1 ORE to winners while there are emissions left. + let mint_amount = ONE_ORE.min(MAX_SUPPLY - mint.supply()); + let winnings = winnings + mint_amount; + if mint_amount > 0 { + mint_to_signed( + mint_info, + vault_info, + treasury_info, + token_program, + mint_amount, + &[TREASURY], + )?; + } + + // Payout winnings to miners. + let mut checksum = 0; + for miner_info in miner_accounts { + let miner = miner_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|m| m.round_id == board.id)?; + let miner_commits = miner.commits[winning_square as usize]; + let rewards = (winnings * miner_commits / square_commits) + miner_commits; // Winners get their own prospect back plus their share of the winnings. + miner.rewards += rewards; + miner.total_rewards += rewards; + checksum += miner_commits; + } + + // Verify checksum. + if checksum != square_commits { + // This can only happen if the caller didn't provide full set of winning miners. + return Err(ProgramError::InvalidAccountData); + } + + // Update board. + board.total_winnings = winnings; + Ok(()) } -// pub fn get_slot_hash( -// slot: u64, -// slot_hashes_sysvar: &AccountInfo<'_>, -// ) -> Result<[u8; 32], ProgramError> { -// let slot_hashes = -// bincode::deserialize::(slot_hashes_sysvar.data.borrow().as_ref()).unwrap(); -// let Some(slot_hash) = slot_hashes.get(&slot) else { -// // If reset is not called within ~2.5 minutes of the block ending, -// // then the slot hash will be unavailable and secure hashes cannot be generated. -// return Err(ProgramError::InvalidAccountData); -// }; -// let slot_hash = slot_hash.to_bytes(); -// Ok(slot_hash) -// } +pub fn get_slot_hash( + slot: u64, + slot_hashes_sysvar: &AccountInfo<'_>, +) -> Result<[u8; 32], ProgramError> { + let slot_hashes = + bincode::deserialize::(slot_hashes_sysvar.data.borrow().as_ref()).unwrap(); + let Some(slot_hash) = slot_hashes.get(&slot) else { + // If reset is not called within ~2.5 minutes of the block ending, + // then the slot hash will be unavailable and secure hashes cannot be generated. + return Err(ProgramError::InvalidAccountData); + }; + let slot_hash = slot_hash.to_bytes(); + Ok(slot_hash) +} -fn finalize_block_reward(slot_hash: &[u8], limit: u64) -> u64 { +fn get_winning_square(slot_hash: &[u8]) -> u64 { // Use slot hash to generate a random u64 let r1 = u64::from_le_bytes(slot_hash[0..8].try_into().unwrap()); let r2 = u64::from_le_bytes(slot_hash[8..16].try_into().unwrap()); @@ -183,7 +148,6 @@ fn finalize_block_reward(slot_hash: &[u8], limit: u64) -> u64 { let r4 = u64::from_le_bytes(slot_hash[24..32].try_into().unwrap()); let r = r1 ^ r2 ^ r3 ^ r4; - // Use modulo to get a number between 0 and (limit-1), then add 1 - let k = (r % limit) + 1; - k * ONE_ORE + // Returns a value in the range [0, 24] inclusive + r % 25 } diff --git a/program/src/set_block_duration.rs b/program/src/set_block_duration.rs deleted file mode 100644 index 5a19e79..0000000 --- a/program/src/set_block_duration.rs +++ /dev/null @@ -1,24 +0,0 @@ -use ore_api::prelude::*; -use steel::*; - -/// Sets the block duration. -pub fn process_set_block_duration(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { - // Parse data. - let args = SetBlockDuration::try_from_bytes(data)?; - let new_block_duration = u64::from_le_bytes(args.block_duration); - - // Load accounts. - let [signer_info, config_info, system_program] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - signer_info.is_signer()?; - let config = config_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|c| c.admin == *signer_info.key)?; - system_program.is_program(&system_program::ID)?; - - // Set fee collector. - config.block_duration = new_block_duration; - - Ok(()) -} diff --git a/program/src/set_fee_rate.rs b/program/src/set_fee_rate.rs deleted file mode 100644 index 03c6259..0000000 --- a/program/src/set_fee_rate.rs +++ /dev/null @@ -1,24 +0,0 @@ -use ore_api::prelude::*; -use steel::*; - -/// Sets the fee rate. -pub fn process_set_fee_rate(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { - // Parse data. - let args = SetFeeRate::try_from_bytes(data)?; - let new_fee_rate = u64::from_le_bytes(args.fee_rate); - - // Load accounts. - let [signer_info, config_info, system_program] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - signer_info.is_signer()?; - let config = config_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|c| c.admin == *signer_info.key)?; - system_program.is_program(&system_program::ID)?; - - // Set fee rate. - config.fee_rate = new_fee_rate; - - Ok(()) -} diff --git a/program/src/set_sniper_fee_duration.rs b/program/src/set_sniper_fee_duration.rs deleted file mode 100644 index 7ead451..0000000 --- a/program/src/set_sniper_fee_duration.rs +++ /dev/null @@ -1,24 +0,0 @@ -use ore_api::prelude::*; -use steel::*; - -/// Sets the sniper fee duration. -pub fn process_set_sniper_fee_duration(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { - // Parse data. - let args = SetSniperFeeDuration::try_from_bytes(data)?; - let new_sniper_fee_duration = u64::from_le_bytes(args.sniper_fee_duration); - - // Load accounts. - let [signer_info, config_info, system_program] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - signer_info.is_signer()?; - let config = config_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|c| c.admin == *signer_info.key)?; - system_program.is_program(&system_program::ID)?; - - // Set fee rate. - config.sniper_fee_duration = new_sniper_fee_duration; - - Ok(()) -} diff --git a/program/src/swap.rs b/program/src/swap.rs deleted file mode 100644 index 279b834..0000000 --- a/program/src/swap.rs +++ /dev/null @@ -1,292 +0,0 @@ -use ore_api::prelude::*; -use solana_program::slot_hashes::SlotHashes; -use steel::*; - -use crate::whitelist::AUTHORIZED_ACCOUNTS; - -/// Swap in a hashpower market. -pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { - // Parse args. - let args = Swap::try_from_bytes(data)?; - let mut amount = u64::from_le_bytes(args.amount); - let direction = SwapDirection::try_from(args.direction).unwrap(); - let precision = SwapPrecision::try_from(args.precision).unwrap(); - - // Load accounts. - let clock = Clock::get()?; - let [signer_info, block_info, config_info, fee_collector_info, market_info, miner_info, mint_info, tokens_info, vault_info, system_program, token_program, associated_token_program, ore_program, slot_hashes_sysvar] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - signer_info.is_signer()?; - - if !AUTHORIZED_ACCOUNTS.contains(signer_info.key) { - return Err(ProgramError::InvalidAccountData); - } - - let block: &mut Block = block_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|b| clock.slot >= b.start_slot + MINING_WINDOW)? // Block has started, mining window of last block has closed - .assert_mut(|b| b.end_slot > clock.slot)?; // Block has not ended - let config = config_info.as_account_mut::(&ore_api::ID)?; - fee_collector_info - .is_writable()? - .has_address(&config.fee_collector)?; - let market = market_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|m| m.block_id == block.id)? - .assert_mut(|m| m.base.liquidity() > 0)? - .assert_mut(|m| m.quote.liquidity() > 0)?; - mint_info - .has_address(&market.quote.mint)? - .has_address(&MINT_ADDRESS)? - .as_mint()?; - vault_info - .is_writable()? - .as_associated_token_account(market_info.key, mint_info.key)?; - system_program.is_program(&system_program::ID)?; - token_program.is_program(&spl_token::ID)?; - associated_token_program.is_program(&spl_associated_token_account::ID)?; - ore_program.is_program(&ore_api::ID)?; - slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?; - - // Load miner. - let miner = if miner_info.data_is_empty() { - create_program_account::( - miner_info, - system_program, - signer_info, - &ore_api::ID, - &[MINER, &signer_info.key.to_bytes()], - )?; - let miner = miner_info.as_account_mut::(&ore_api::ID)?; - miner.authority = *signer_info.key; - miner.block_id = block.id; - miner.hashpower = 0; - miner.seed = args.seed; - miner.total_hashpower = 0; - miner.total_rewards = 0; - miner - } else { - miner_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|m| m.authority == *signer_info.key)? - .assert_mut(|m| m.block_id <= block.id)? - }; - - // Reset miner. - if miner.block_id != block.id { - miner.block_id = block.id; - miner.hashpower = 0; - miner.seed = args.seed; - } - - // Pay swap fee. - if config.fee_rate > 0 { - fee_collector_info.collect(config.fee_rate, &signer_info)?; - } - - // Load token acccounts. - if tokens_info.data_is_empty() { - create_associated_token_account( - signer_info, - signer_info, - tokens_info, - mint_info, - system_program, - token_program, - associated_token_program, - )?; - } else { - tokens_info - .is_writable()? - .as_associated_token_account(signer_info.key, mint_info.key)?; - } - - // Set the sniper fee based on time since the market began. - let fee_rate = calculate_sniper_fee(block, &clock, config); - market.fee.rate = fee_rate; - - // If selling, limit the amount of ORE that can be transferred to the market in 1 swap. - // if direction == SwapDirection::Sell { - // if miner.hashpower > ONE_ORE * 10 { - // // Scale down linearly from 10 ORE to 0.01 ORE - // let scale_numerator = (ONE_ORE * 50).saturating_sub(miner.hashpower); - // let scale_denominator = ONE_ORE * 90; - // amount = (ONE_ORE * 10).saturating_mul(scale_numerator) / scale_denominator; - // amount = amount.max(ONE_ORE / 100); // Minimum 0.01 ORE - // } - // } - - // Execute the swap - let mut swap_event = market.swap(amount, direction, precision, clock)?; - swap_event.authority = *signer_info.key; - swap_event.block_id = block.id; - - // Transfer tokens - match direction { - SwapDirection::Buy => { - // Update hashpower. - miner.hashpower += swap_event.base_to_transfer; - miner.total_hashpower += swap_event.base_to_transfer; - block.total_hashpower += swap_event.base_to_transfer; - - // TODO if base to transfer is 0, then fail - - // Transfer ORE from signer to market. - transfer( - signer_info, - tokens_info, - vault_info, - token_program, - swap_event.quote_to_transfer, - )?; - } - SwapDirection::Sell => { - // Update hashpower. - miner.hashpower -= swap_event.base_to_transfer; - miner.total_hashpower -= swap_event.base_to_transfer; - block.total_hashpower -= swap_event.base_to_transfer; - - // TODO if quote to transfer is 0, then fail - - // Transfer ORE from market to signer. - transfer_signed( - market_info, - vault_info, - tokens_info, - token_program, - swap_event.quote_to_transfer, - &[MARKET], - )?; - } - }; - - // Validate vault reserves. - let vault = vault_info.as_token_account()?; - market.check_quote_vault(&vault)?; - - // Update block reward. - // Use first byte for limit on current probability disribution. - // Use second byte for steps taken so far. - let slot_hashes = - bincode::deserialize::(slot_hashes_sysvar.data.borrow().as_ref()).unwrap(); - let clock = Clock::get()?; - let reward_bytes = block.reward.to_le_bytes(); - let limit = reward_bytes[0]; - let steps = reward_bytes[1]; - let (limit, steps) = update_block_reward( - limit as u64, - steps as u64, - &slot_hashes, - block.start_slot, - clock.slot, - block.end_slot, - ); - block.reward = u64::from_le_bytes([limit, steps, 0, 0, 0, 0, 0, 0]); - - // Update swap event hashpower. - swap_event.miner_hashpower = miner.hashpower; - swap_event.block_hashpower = block.total_hashpower; - - // Emit event. - program_log( - &[market_info.clone(), ore_program.clone()], - &swap_event.to_bytes(), - )?; - - Ok(()) -} - -pub fn update_block_reward( - mut limit: u64, - steps: u64, - slot_hashes: &SlotHashes, - start_slot: u64, - current_slot: u64, - end_slot: u64, -) -> (u8, u8) { - // Calculate how many steps should be taken. - let d = end_slot.saturating_sub(start_slot).saturating_div(10); - let target_steps = current_slot.saturating_sub(start_slot).saturating_div(d); - if target_steps <= steps { - return (limit as u8, steps as u8); - } - - // Calculate new limit on probability distribution. - for i in (steps + 1)..target_steps { - let sample_slot = start_slot + (i * d); - // If reset is not called within ~2.5 minutes of the block ending, - // then the slot hash will be unavailable and secure hashes cannot be generated. - if let Some(slot_hash) = slot_hashes.get(&sample_slot) { - let slot_hash = slot_hash.to_bytes(); - - // Use slot hash to generate a random u64 - let r1 = u64::from_le_bytes(slot_hash[0..8].try_into().unwrap()); - let r2 = u64::from_le_bytes(slot_hash[8..16].try_into().unwrap()); - let r3 = u64::from_le_bytes(slot_hash[16..24].try_into().unwrap()); - let r4 = u64::from_le_bytes(slot_hash[24..32].try_into().unwrap()); - let r = r1 ^ r2 ^ r3 ^ r4; - - // Use random number to get a 30% chance (3/10) - // Since r is random u64, checking if r <= (u64::MAX * 3/10) - let threshold = u64::MAX / 10 * 3; - if r <= threshold { - limit += 5; - } - }; - } - - (limit as u8, target_steps as u8) -} - -fn calculate_sniper_fee(block: &Block, clock: &Clock, config: &Config) -> u64 { - let elapsed_slots = clock.slot.saturating_sub(block.start_slot); - if elapsed_slots >= config.sniper_fee_duration { - return 0; - } - let fee_bps = - 10_000 * (config.sniper_fee_duration - elapsed_slots) / config.sniper_fee_duration; // 100% - 0% - fee_bps -} - -#[test] -fn test_sniper_fees() { - let config = Config { - sniper_fee_duration: 100, - fee_rate: 0, - fee_collector: Pubkey::default(), - admin: Pubkey::default(), - block_duration: 0, - }; - - let mut clock = Clock { - slot: 0, - epoch_start_timestamp: 0, - epoch: 0, - leader_schedule_epoch: 0, - unix_timestamp: 0, - }; - - let block = Block { - id: 0, - opener: Pubkey::default(), - reward: 0, - best_hash: [0; 32], - best_hash_miner: Pubkey::default(), - start_at: 0, - start_slot: 0, - end_slot: u64::MAX, - slot_hash: [0; 32], - total_hashpower: 0, - }; - - for i in 0..200 { - clock.slot = i; - let fee = calculate_sniper_fee(&block, &clock, &config); - println!("Slot {}: {} bps fee", i, fee); - } - - // assert!(false); -} diff --git a/program/src/whitelist.rs b/program/src/whitelist.rs index 6edf2a9..98ff543 100644 --- a/program/src/whitelist.rs +++ b/program/src/whitelist.rs @@ -1,240 +1,241 @@ use solana_program::pubkey; use steel::*; -pub const AUTHORIZED_ACCOUNTS: [Pubkey; 2] = [ +pub const AUTHORIZED_ACCOUNTS: [Pubkey; 236] = [ pubkey!("pqspJ298ryBjazPAr95J9sULCVpZe3HbZTWkbC1zrkS"), pubkey!("6B9PjpHfbhPcSakS5UQ7ZctgbPujfsryVRpDecskGLiz"), - // pubkey!("J89R2jNKbfkFoJjvkjnwwepvJRE2M8VPQ67RhPeQfVY8"), - // pubkey!("6Qaf8uCcYWkWb12FZYUhuqkae3np2WiaZCv7ic4PMf72"), - // pubkey!("DQLBoeyCkUuGMHmsEBBJ5LMzdXDza89NLEdzkbtjMfXq"), - // pubkey!("Gw7kkmtMp4abR4KHjDK3rS5XAQR85GSDHNRniTQR9t2n"), - // pubkey!("BiK1WCFE9a8eX3eEJRCTw5QVpofK69hXdBwKMV4ANKdi"), - // pubkey!("mtnDu5GJeWHFXzEwV6RbocsigkGmvDorHj8Tw1SPeYQ"), - // pubkey!("9cpGSYpRthttGo3QvidzWbd3nseHP3fGSURQvqsih7dw"), - // pubkey!("BdBhzGbdBb2JvaPJbpNxnPKqifWdatvckJoWugqf1gGd"), - // pubkey!("7NhPqxVw9VMJhcEsw1NSRkfuChCCrm6vRg4s1uUMrSUY"), - // pubkey!("3edXHybYX37pFU6ajwFmouLAaoVT3Y9g5UxwFqhMEqCB"), - // pubkey!("DcV4GCNgLv8viyaa9H8Msy6P6U45MbipiHZXjJumoVnb"), - // pubkey!("6GQUHWkfKgkFkNdqyStS1Sow1nAvFDCxoY8S6prU3ptQ"), - // pubkey!("1andmzF89uqE7HF5uzFLyMPEnoKDjgmaccVu917Esqg"), - // pubkey!("9uqkcbU7oecQPdASKr7GrBEteS5BGLEGHeFHWxpNN6sw"), - // pubkey!("57KHNNE8MUMxTfee61e3WttDn7xghbHYFqpnN9aVtA7R"), - // pubkey!("BoTrd2M287L59VmoHLJXGGj3Zfk8F93QtCPLqasLDPW4"), - // pubkey!("2uki6djGnWnC6SfN6MvcZPtn1hZ7N8of54Rimuik2qup"), - // pubkey!("4FXiDN7mV3NteqUMrPJsZaUVpn6vsghnYdEnhYJstdyu"), - // pubkey!("8RDJcd66btm6UxYozrzkQaVNo21CSsDHWzE7Buy3eyys"), - // pubkey!("9XhUhyaaxNLpzy1ZSgAYkyoU4K2kcS7HBp72jSegPy43"), - // pubkey!("3ucoQSjg6AVpSotpZRCoHV82v6A1hNyMe6kX8Ag36qG9"), - // pubkey!("ECFuMRESmaWivsi7rha4rDmwuKeN8xppEophf6sNLgUx"), - // pubkey!("BQbXD9tqv3ysrvojSZao2RoW9ucR4RmiKbKEaVWJp4J3"), - // pubkey!("2jVdMx7fb88txbG6YoZzC7kT4Tq8rJDaWrNgbZ3ZnqCb"), - // pubkey!("563zd49mogp48wBC4XEjzPQ2hZNViSTPjiN8QUteQjMv"), - // pubkey!("Bw5zgt2ewuYDB63VtU5LDNNi8GLUwcEqE9iiuLwWUPyq"), - // pubkey!("2naDt6dWy5uF3vbWKVxddTKmdEg3FaF1d89CBe3EaqYn"), - // pubkey!("Cy8HEkWe4xqC62A9d4ZUzHS34CugT9SNLxgRazXYibW"), - // pubkey!("Gdmd1M2LAYvU3A8R5imZHeNSguVNJDGNhprtyMDADrAq"), - // pubkey!("7quEeAYjHqXsrR5MNRtsLKz3vfZL7CtoaLjpaFotqaFu"), - // pubkey!("Ecu3AFZM8vHpEEfCDU7ecTMY8eMiXpPk1df3FrTTtR3m"), - // pubkey!("Ak9DsM3BQg47CC8YgGbUiMSZgJyUxqjvckeKybeJd8gn"), - // pubkey!("E1L58mNnUc9STrauT3TnR8Qb6EGfjqSmHBFNyP81ii1m"), - // pubkey!("Enz7GA4a6hZ8UKtM4urov9HnH7PP5kkjs5Yggg7BYeZT"), - // pubkey!("BREFixw21JPC1Vwad2tVCCUD4qrD79sKpKmbqgfkrvCz"), - // pubkey!("FhEtsokybH9eqEEbreQqxwndUSYz6jvbiYjA75oX5faq"), - // pubkey!("G9a3certFcZbWkrsvzq5E62CxgoBtEv5NFT1wjr48SAB"), - // pubkey!("6BNk6UhhpaNE81MscSKnTVGnpUeptRyNPDRQxzD42LuH"), - // pubkey!("7ry8P6VPABw58QcKjWi2ksgcziqdX5Woc4NubiQWXwBd"), - // pubkey!("AjJk2eKmCUUXkcFQASGdcBxNh4UL2MuwQ9Bjdq8RQu5"), - // pubkey!("DmzDHcbMFx2buuhWSHq7uk9345A6awrQg5dVLtSWXXa8"), - // pubkey!("91wUgZtGXWJzQsFKq1T7xrgTaCYWLsLcwPko38V8FjUR"), - // pubkey!("DVB69rna82Y67tn3Sgiy9eTjZmtwPppdsYv5RBLfQfJd"), - // pubkey!("Hudzsqx4C99GEhZVzjzsjKZoF6kooUZdBDWTKJFnAcYk"), - // pubkey!("F8GDWM1WPmnaRPJRpRuSm2WLfXLQFp2abiBorynpWmvR"), - // pubkey!("CQh1EwRmscHSE8jna6aUxAkjvr2VwLK96GWQz3tbmKqA"), - // pubkey!("6x6u8VZct4hrhbwUxyATaHNqCZytScFXGWkdwqG7oK3u"), - // pubkey!("MadFFXJx5oqYzJyoRtMgDm8PwSc3BwJpvc5uaEiMDuo"), - // pubkey!("582Q82NVuUespuZ5xwtJZnR9UuMcmXqmUgSysBSx2VtQ"), - // pubkey!("GsPisQ2H2FLifx9T2FkMxXAxBaSCVX4qZNP1yTrS4DUA"), - // pubkey!("EcVpECeotzrs2XGcrDeBiDg8FhN55g4aAgjKJja5QQXb"), - // pubkey!("2eUFNjWuoSEQTkgvfHdrPcByXkwA7pTBiyZi95u46PJ1"), - // pubkey!("4RGNSXy3QJTSeQgmhqKvvNhhkpD7KAb3rok2tWCSQ4zq"), - // pubkey!("BobjP8dCMCQ4LeCqymDzHqDcyQNTGBBbo8gNG3N5Tk7t"), - // pubkey!("BVKmb7UTRUoQriQMUVfpH8f9jn539Q154jGWzPVRCPQQ"), - // pubkey!("VKPqFvbo4DuCTBCs4rvtGDUTdGEAS1R82R6Ep8KnzL8"), - // pubkey!("JUskoxS2PTiaBpxfGaAPgf3cUNhdeYFGMKdL6mZKKfR"), - // pubkey!("7seVHpqVocNhW93j6GCbX4BLHNoG17ug4s62TCsR4XyC"), - // pubkey!("DeKg3DbqfV7bdmHzpkc5GY9R78zKWPLqZyn6sh2TYZmv"), - // pubkey!("FLNQ4FHUAy8be2LJgggunjg1tAehVvt1eQDpxjmWVeFA"), - // pubkey!("BmnChLJUQBWaHDpwurTtuNpqZkPf6eMNbgxP5Am2pL62"), - // pubkey!("2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf"), - // pubkey!("6fFWA9BLyqNSSRcKvdyGtLrgaTzx31fs2oLaQnrSw3Vr"), - // pubkey!("DbswyzwwS1LRYKSxvwJbyE8SVxUYurfWJEu1mUdXytjQ"), - // pubkey!("6KW3smHmstPUhAbmq9CohiwbXmWcq81jfRehEE7xQ4ia"), - // pubkey!("Bwmo2vpL8QB9PStEbdg4UZhyUwA7q5HHhyJpPVa43Aeq"), - // pubkey!("CPYRPM421AaXBM5qGShnWsoiZMVcrCCS3JYfPG7SYYAK"), - // pubkey!("3KFstu1jDPb4TByc9r4915kYkghkUNAyzQsASQGuPZWD"), - // pubkey!("884aD4A8GyFBxqmuP6QQ1Gi2EwucFykwXFVkdom6uhhu"), - // pubkey!("CniJsr5gxFxhL35FVWFtYtutajKjn3wYQt33puTNADTc"), - // pubkey!("BixZzjYNyYGi1ywdHHekRCkxcQwNvtCKxxGppNCecYbT"), - // pubkey!("387EhfcBR7Tj662V1PrzhbpaHPab4YddAPTKPf21wwkk"), - // pubkey!("6jr135RZTwHfTmuxSTbZvoL7TQm1Qd6Koqfu28DaE7XN"), - // pubkey!("8PwomUFs26C74SCMF2S4cMx1aujZ3i4WCihE19Ztm7n4"), - // pubkey!("3bNurM55JJyHTMYfMJw27gLSHKknthBkhSazAi9WX2or"), - // pubkey!("5eefJGcPYCqGWDzUEMpBwRFyE8DfvqwqDk9goFjXGkEn"), - // pubkey!("4r2wk9KM1wjrUhmdUpv9SvuFUDwfpRUP8ezxn22j7eXX"), - // pubkey!("BVtYZDPpsrVTnwRFNmGDbg64uToGS3mxxY9U5MAZ3Nsw"), - // pubkey!("DSZFvyL8vwUZ8i67koaJT6nA26t2VhWftqCrYp2sxbQr"), - // pubkey!("RKm9CQxvwi9iN331Rkwktm6achj4JW5pFYsKXb4xfeG"), - // pubkey!("2zahht8VNqDoUSgr4gyUXXzkr8xziyD7aTBNi6i9oAHb"), - // pubkey!("DubBYMVsQeQdFNMR3ho3fDAQxqSM2SVNc9HsQXnzjJ3F"), - // pubkey!("5onZdAogWbxCzebwvfeidNeX9VgkQzbGpDuwS1THkhuV"), - // pubkey!("2xTu2WHcvt5VwbdwBr1MkfV72jyy9phd5ksfrGy3tyma"), - // pubkey!("7ceTUBq2k8pNjrcjyWHapmhP51J1ttyKUHWk1fcqh176"), - // pubkey!("yVqjGGK34PgKwhEaeyp4UTLcQiJQaKTPrnbJADaMj9x"), - // pubkey!("2mAShJV8RduZEUMvYWDAZgttRzWp2knc5EHoVszuVoFo"), - // pubkey!("6Z6PsSvbQHndjqNTFZUEYLpouFcExYxaceCHAyHGNgMc"), - // pubkey!("FTX9nCXBBczYYtFxi1pus76YQt7XupYx4KVY36FoFepT"), - // pubkey!("3SK1R1wWABFtCBRpyCUe7vshx2vew4m52rCz5V5Ynt1F"), - // pubkey!("BQiMrFT8Kcey131zfk7jKwvS4esvFGmLreNPP6SfoF4R"), - // pubkey!("5ytZTkw2noiFm6Zu6pZcQ2Yeg1EBnRnxY4dKpNpYF4qL"), - // pubkey!("7qCM9LFQyW49TX7Dp8GaqQ8fytAEEDs2CJ2Td1jLtzXf"), - // pubkey!("9ZFLHxu35yZaB1syp7L384PpvtgUX525M38RrA3CM9bh"), - // pubkey!("EVrX3x3zfjKjY2baajZGUTwrgbKFkw1iQP7FKAEuRLF4"), - // pubkey!("BRa2oSPwuTebwsc28jbtMxsYtuvqbPoJkVM7n9q7zd5o"), - // pubkey!("3B4nPAvYGo2iXxvAHRktcFBexAiU2wkqFaywK4wtM1dn"), - // pubkey!("6yQQZhoNDd4qLwFn4LcQuH5XEdCRQQgy9o15DMLot4pa"), - // pubkey!("CD9QsVyyWvmUnuZeJJhQ2VzWAYvkKiRpCobpJWFv2YkD"), - // pubkey!("D1w6h4EqQZw2RYped6Y3RV2jDeF2a4TFkNoySbNi7j9j"), - // pubkey!("9KRVJDMDAFHsQ3FD6QXsmQAEf8XAXgFHVWeQcStBrtME"), - // pubkey!("73FhRUQPTQrsyVMjLCx64ibqUD1egynyhzrcL3qcW2gi"), - // pubkey!("Fj2HgotRPACCTiLK3qXPtrx9ez6EDgVpUKn9NQUa1LRu"), - // pubkey!("GpwDBLw12TCKMQkqJEGHppKcV1ULBPfXZV4RFwWftuxY"), - // pubkey!("BRgdUnFLFKFNR4WF9eWAesez67D7mU9mri1pjd7sj8P8"), - // pubkey!("Gv3mxjsWGH9FFvwdqE5YiihNSGeMaNY4V1A9xxfRtwWB"), - // pubkey!("AN3tqhYC8TkugoyFzmMCTwnEaNpDDyTSxBjpvZrpDjuN"), - // pubkey!("8fTkqpcqUhAs6KbrbkrZcMLKvrf5WymWizRi7v2TFqBY"), - // pubkey!("3nk9Bz8anhjZrRvHd52ZwG3JLasHUFKU1xbo4vB5XekX"), - // pubkey!("E31Z3uXrzEFMxscd171QH9aCJZiMWLCGYbVgwcHx2G2z"), - // pubkey!("D9FyQGZcmJPEv4gAD7oH8BTpj4VQiwFEKyCvcQMgYAY8"), - // pubkey!("CzQYL5eL2ZQH28yvk7brWotjj7TrXaw55nasGnVYyLWn"), - // pubkey!("aa1AhZSe87ordRejNkGLaJN8vs71t8WDD3aYfZNT7cf"), - // pubkey!("5Z9UpPrFQTeKeJeKrgEWYcSG8MFN3YtAtdWw7JLhLJHL"), - // pubkey!("85j4tHFBabkGEnoGPj5eGuYz3oUtqgZcHkft6aYkt1er"), - // pubkey!("AeeDSNJicCbwTqDfbF83TA4MFaYP1dZujyTTQm1bdQEe"), - // pubkey!("xrykqd9rb64yNZeoxzHLAGFPrFgkN65w3aEJ6KqzN74"), - // pubkey!("AkifiiLputLYjN9BdWvQF3ug2odhe2GJBUXmdGVENKsd"), - // pubkey!("6jy3U836vS8omfhYt4nEzQyyXdBYzQSiDskSJTUTS71n"), - // pubkey!("GRssGjrNG7XtmZZK644VTN8BkA4UEWSmH2cke2Zj6prf"), - // pubkey!("2ww2LiHU2hdCCiTQbvQ72PEgUBrznpmLpTUXCBJZzLPa"), - // pubkey!("4o8R93WLNpj6kwc8ka8ptLW7dShb2hoi4S3dFLqRyz3E"), - // pubkey!("EA7jgT8r5K4r8iFhxhGxRQYYaQK1RoYKyZysaNm5r8ZW"), - // pubkey!("Gi2JbovBxEgRyUrf8tg1VE3UZqdQ5fHB4csUKG85cTA4"), - // pubkey!("7YMFFjzKFAZVauacGPvtxuVngTbxPsv6EM4oiqjxMYtz"), - // pubkey!("CSVmNyCwaMtJbvYCH7JbN7GXF5TABrdTUgxy4VpHQ8Kb"), - // pubkey!("2jWN7kds7jBYmHr4fCXu1gMJUkgqJCJoVwJ1Rv996jSS"), - // pubkey!("663A19n8DaCXdaSycFwmTxXeNBKFMZunoPRoy6LDbK6k"), - // pubkey!("E3onGu8Fe2bd3pCpFaXmApaUB3JuB9neCqx6YTh3Lo6R"), - // pubkey!("E4DwabawCYTZwqNv5LVj3LHZzPP8XL6xuya2C6mkJMwZ"), - // pubkey!("5YLUmcpRKyY1YQESMTFiwoA8moTGcjSLadoRC9PxgC5P"), - // pubkey!("2RiqNWDWgJnrX2G3ahBJYmt3ab56K5HXFzT7WGWf7h4X"), - // pubkey!("HAXeS5472XnL58J5Wq83qc1rZqc2Dud7Yo44A6aMakDs"), - // pubkey!("8LNCzYSVijuFDxHH1K62B5jo72W7gxicB3QURBfHkMbj"), - // pubkey!("5MRLosQFPcmqL2DNBLKmW5dmmyXF9jjx7sA8QK8Btd7Z"), - // pubkey!("13V7kcaGMsXHTbZNFStFySXA2yoLAupHXBkPG61AV3F2"), - // pubkey!("EcSUyx1axTPa7CtbiNiKo8c9YTmSigLyPG9mLBRmfts2"), - // pubkey!("5qrvvuCB5Lo75tdyD2kRaGASDanZh9b6EX4VSWWmuXEf"), - // pubkey!("58uP5UWXaZVv19uoGX4MqqRS2L6bA7tGmxxZK51QXFKL"), - // pubkey!("6EFuKUZEaUCGHsmei3NEJhgDX3458YTkEfQboGNj5x6n"), - // pubkey!("75NTaCnZU42fsjqEdr8gKbmx9JHUXeeXzXFA7HG8JNea"), - // pubkey!("5TkzZbno1x5t5MBEoBDXdicckAoMCABnWVLS2ntbk6hF"), - // pubkey!("CK8ei4KVemer7GznCQK9eu3BRQMBLXXieMAbJAeHMcoN"), - // pubkey!("E9sTbkJ4Sxcvmy774auV7wxh6vKt9TXV666z4WR4NybT"), - // pubkey!("E79uaVcz8KpQUSNVA2JGX5rqM6DuWm3KZThNvBoqGRFn"), - // pubkey!("93skKa6mstJdK9LF1UDRVEv9QVpD8738JpfTtHmxBTzx"), - // pubkey!("CiBaXEQxvStqkSEGAYPX6YdCnU4JzndLgFNGh1Fr4Pd"), - // pubkey!("Eu3KDfvMU4PXSqNHFU4MFVLfR3BDwm3cdNqMgEwvqpiN"), - // pubkey!("5fAtP5JMbMjVuz3Dt4XbnjdvQQaXKopuuJqEkDYumy6o"), - // pubkey!("6Eqy9tnGg2RMqwM8i1NXCDj37EfAzniKNd87jWA6Nhou"), - // pubkey!("DgXUUwsEw88fbDi7FmASgJfDR1mFYJnUBSpT46aBtp35"), - // pubkey!("GkPpc1auG5FPgqQXYytEUUndZstZESh3h3bPPJ9jyZ4b"), - // pubkey!("8fXvsNFwihfyKRDEv4oAhM2E5NWSNBMByKitZHpUD7Tj"), - // pubkey!("CX47idzWhK8cDLpUNYraPFoieGxy8wzR9XJF3Avsq6nF"), - // pubkey!("HoeivdxHbL7pFY7CyhZP9G8eBHouRw3ckkSnekj7Zh6u"), - // pubkey!("69m9TPRfb2oYdquNDKu7XRfSaYyKqv9D2MamcyWSeGj7"), - // pubkey!("8525tNnMAxPLqNX3t8EqgRzhKvUQo6oUV8ovUnLWPWD9"), - // pubkey!("FzfTigLWBhVzmStXoU91tBoWXjKqqyEMLzPMuwm8xj53"), - // pubkey!("5rUNTDMAFDdvSQEHSpNNtUVRo5aKdPfLRsKsWm9t4Lv7"), - // pubkey!("FKSNp1wnJMabfTPJBD4M2WVPTMwJoeJ7gDiGu1ZW6nFe"), - // pubkey!("8TaGL4S4vx3tyZcnr9fV6A999MfCsdC14QLAB2F9eVnm"), - // pubkey!("UuGEwN9aeh676ufphbavfssWVxH7BJCqacq1RYhco8e"), - // pubkey!("HW252Pdt9qsyakaBepVWtcEKesMLJKCnYJy9MmZNtng1"), - // pubkey!("5YLjGCCdStdL1WfAmeSUU6P2X71Rkc1poxKidcbA3KK5"), - // pubkey!("AwScekxPFJchPcWkwuKMb8aK8fjEw5o7BGTM7eoofk9A"), - // pubkey!("Hzs2oCTDfEXQc28wMzMujKpDYMMotYaRkdQdtHZNjh3H"), - // pubkey!("H7QppzedhVRWbG4vGRVGSPP8pcmBFqDKC31xTEjLnQHA"), - // pubkey!("GjmLjF2vhmLgbEy5VgEZz7gWEzyCqZAXMBsKBsSk5ev4"), - // pubkey!("HzJr3T2qHesCVkQZcQJK8nZWkns1N9qj4Hj4dHi8ZNP8"), - // pubkey!("BQQYZKHfNhmUrdU2UhwZzWdVpiVzdBCa3qLerhDrcbAs"), - // pubkey!("7LfVG4MNxTenrhw4xczt3ToFL6jd3KUXSzKBMpxambtM"), - // pubkey!("DcBJoxBb8KNqrwdbMKFSR8qjyYYpTy1Pu7vU5K7LZWNn"), - // pubkey!("DGs8ZrfatMpLza4ekXDdq5mmEmt5QqeKQ1q3WrpsrM3s"), - // pubkey!("4DGxxu1fTbteKXm6USy3enW7S18iPFuJkqhKrSopGeBS"), - // pubkey!("AkmZXNFjEL6LgxGi1eM81iTXfwNJgeFG7iXUiFfmHni8"), - // pubkey!("297ogus15jvgePqXZCwT8nB1gvwgCJYdKcuXKiyH4TfS"), - // pubkey!("3yKHWBKD5DeX7vG2ESJwWQWF4HgmgHumeXPhaZnqiore"), - // pubkey!("4AQBsVmECmSBPh4JicNhGaT9waHETNxeNkaz72tezgSR"), - // pubkey!("6zWbGFC9WgPymyrTFM2MKAwLu8vKXwZJjFUcksikdabE"), - // pubkey!("Xp7Swytm55aTD8onDegFAVm4gC7zdCCkYMobRg5oHfr"), - // pubkey!("3rB1eaJeFYKHwRP1q9mAVEYxY2cCJkHx98yVppB6tPuu"), - // pubkey!("ABcnheNNaj3q3Yi1pbDrEhSjKJYqJcn4RY4tZcurdXYz"), - // pubkey!("CwkzrFZmFPqX1x52uqRV3d1JncgmMiCKTxScJ3XSVA4A"), - // pubkey!("3t9CReK1B7z1B4sTnfWrDZtyGaX5pV6ydXvE1vanJ4FC"), - // pubkey!("G8d3gRGvAg8k9oiazKPPAA49BgAJuPACFbdf5CRWjpaZ"), - // pubkey!("gpoo1atPkrKnfxQ4Qt214ErbgBBJeiksL1EjqBHynbo"), - // pubkey!("CFRt6hxJoYQYgS9jVZHQEYMpEzfRLxAo1vwq7R8PnLJ7"), - // pubkey!("D4b7ocAwoUrCM7pZZYfqyAftHXDVhNwbw9JmnrAHG6z6"), - // pubkey!("BntbD7PCAXPhXANT2XibNgduJS1UL2dysUEPMd9gLeJy"), - // pubkey!("3rcwuJQTBG9D5d6P2TurtQQTaTVkw4HPufWGCdhcGqfm"), - // pubkey!("BLtavXya3V2o9BvREamtdn2tgG3o2tGp2qpKr2VbavWg"), - // pubkey!("91rV3hD3ZfMC1smebh6nix4mSUePXLewsxj1Ncz79LD2"), - // pubkey!("6MRghTVnMEXiubLs6V8s3bzz2FEke5UugwR7ByuioB9H"), - // pubkey!("7HZ222sahftEBj8JdyHgvy1oGQTMYjtdNSUZDqzfNyiD"), - // pubkey!("G5SXciAG8aacJ5F4nRAPzDXfn3Rjfbwn1EMvEccrFnjZ"), - // pubkey!("EoB4LgmryBX1m5YmZe8wohQzFssjMhEK1DWwcnk5Gmo9"), - // pubkey!("GtCneCahPq4m88zQxKVKnZM72qdbSUEDp82UDu8iNLWE"), - // pubkey!("HfuUoJHkdxnXb3wCG4roMkeBA8nX4XpqFm9VDwtNQtT9"), - // pubkey!("GbD6i4SxcD2Sqae3wCPvvHpNtr9LxahtHHC8wfThmr95"), - // pubkey!("G4WwpzfCXPzAJ2jfLXZRk7gNTEfaD8jNss5Xij33N21e"), - // pubkey!("Bo4nGugF3usS4N797H4LEm2r2d794kvht6HruwNCVe7Z"), - // pubkey!("2V1pTma3ZcctFvT1tALnoc2W2u5W1DmDqi3BjqerHrCN"), - // pubkey!("BX2X2QYU3twF4bRX2Pro4ARUNXi9cDd7cpRdZFW9JWC8"), - // pubkey!("DF4Ad2CRWyR5KMgGaqcfG258twr9LmsVc7hSCk8Pizfb"), - // pubkey!("AonibGzhwQ2MTtYNXiaEPnrfQ2c1eqJwp8SDfZL46TL3"), - // pubkey!("5o6PCwxNYpoa6wdtvaTiYyKrE9FoNE1bT542ZTuNgJpr"), - // pubkey!("AcVvuR5PoA1Mq5W9UY9x6fEAJthK9a1R4QHWQmETDb8h"), - // pubkey!("367Hqa8Q1DY1p5jKEmpCcJiyj5fH1dfbD7ZUBhCBbEa7"), - // pubkey!("GmRC6EhKtBEcKM1bjCBzamWDy3qmP5z2Kc9tYjQuc2Pn"), - // pubkey!("4iZPMVzyGGTnF3hA45vrSFP32tZ7yKiyQd7MiJHv8dyF"), - // pubkey!("3yxPRUpxUL2hLMLzDTWQpAFqY82MCphgy6iik3J8ZDLr"), - // pubkey!("31KPvwdWKK9FVPAER5fXLHfCCBQ5wGyUSqFMnpF9Xvyj"), - // pubkey!("4KVqRjx2Dyo53wANTGh5QKQQbatqUj3wfP9ZV7YinJTP"), - // pubkey!("EEPoEVgsabibKaAxs21JPBCMqKHmDcWkWzvXjS8vPf6L"), - // pubkey!("9xUqwnwaHnmXJifpJbu2dYu2PybVE2RDZhB7SHhLU2tL"), - // pubkey!("27R5t6DAWFnMXP3ZHA4aXabtxZ2nP4qH3BVQN1oEWSkj"), - // pubkey!("4hewNZbUaUziPGQ1yTmSX2yx9syzkc664RNjfFFS9sK4"), - // pubkey!("EpkrcNkBaK5eLwMKpV52H5MjPxcdWxnb1FcBr9FUEmMt"), - // pubkey!("6pKuMtqh56yaWT6ToYk3F7WcZnowAv2DPuptkYq9pPKc"), - // pubkey!("2icoVmEXH2q4zMmHChnFPS6iDt3hNTPrz3pdEEeK3Dqo"), - // pubkey!("gjwc2FafGF46Yn8aSnQu3a9S5MN1knwkc99yTzyqHnR"), - // pubkey!("A4ke6mJAL7muUJf4QfUV8yLMr2A3nH5Qnu1shV1DefvV"), - // pubkey!("CpF8aa81tZ6uAEoHHK5N79SREL49JyZxjMLPssY6qqU1"), - // pubkey!("8ujTq7pjihTBbebirvHohqykFLvNLa37MLTtFQcp9QHa"), - // pubkey!("3rXinbzxFTQ8uJTEDbU4XUD4gYBaRkfg6DDqpEZKwepf"), - // pubkey!("9zKXwnQU4F2yr2NdAAfzu6XGJQCWmgM5fMyn9qsRVyhj"), - // pubkey!("AVYG9UHetNHT1FEDPLv9pN2sCCH4CLsjvkzGjVXBfEiS"), - // pubkey!("82dYc4N5KJvMyAiSRC74D3uHyNGy8Wr4ghfer15YLQa6"), - // pubkey!("GEwUNpFEN4q4i8RBxmjQHtLC57tmQkUVDhFrDiLjxv5P"), - // pubkey!("B8esH7ZNMHVwm3gQuzeM3XEa8pUa1ELRCYTezwRsMcjA"), - // pubkey!("6Mn9Th41tmNqXyPS8y6hno2EB6wzHcYkfnh6br3NGNAy"), - // pubkey!("ECHb13JeXfPdj5Z3EEFQ1vcGEpagqHXrWGZF7NdaWF4z"), - // pubkey!("FBsBn9iScLsvSe9oUtQiXuyXGh2uJZfwAAzzD8gP84AU"), + pubkey!("By5JFFueXCqeqLk5MzR8ZSwFxASz3SKWX2TVfT1LTFbX"), + pubkey!("J89R2jNKbfkFoJjvkjnwwepvJRE2M8VPQ67RhPeQfVY8"), + pubkey!("6Qaf8uCcYWkWb12FZYUhuqkae3np2WiaZCv7ic4PMf72"), + pubkey!("DQLBoeyCkUuGMHmsEBBJ5LMzdXDza89NLEdzkbtjMfXq"), + pubkey!("Gw7kkmtMp4abR4KHjDK3rS5XAQR85GSDHNRniTQR9t2n"), + pubkey!("BiK1WCFE9a8eX3eEJRCTw5QVpofK69hXdBwKMV4ANKdi"), + pubkey!("mtnDu5GJeWHFXzEwV6RbocsigkGmvDorHj8Tw1SPeYQ"), + pubkey!("9cpGSYpRthttGo3QvidzWbd3nseHP3fGSURQvqsih7dw"), + pubkey!("BdBhzGbdBb2JvaPJbpNxnPKqifWdatvckJoWugqf1gGd"), + pubkey!("7NhPqxVw9VMJhcEsw1NSRkfuChCCrm6vRg4s1uUMrSUY"), + pubkey!("3edXHybYX37pFU6ajwFmouLAaoVT3Y9g5UxwFqhMEqCB"), + pubkey!("DcV4GCNgLv8viyaa9H8Msy6P6U45MbipiHZXjJumoVnb"), + pubkey!("6GQUHWkfKgkFkNdqyStS1Sow1nAvFDCxoY8S6prU3ptQ"), + pubkey!("1andmzF89uqE7HF5uzFLyMPEnoKDjgmaccVu917Esqg"), + pubkey!("9uqkcbU7oecQPdASKr7GrBEteS5BGLEGHeFHWxpNN6sw"), + pubkey!("57KHNNE8MUMxTfee61e3WttDn7xghbHYFqpnN9aVtA7R"), + pubkey!("BoTrd2M287L59VmoHLJXGGj3Zfk8F93QtCPLqasLDPW4"), + pubkey!("2uki6djGnWnC6SfN6MvcZPtn1hZ7N8of54Rimuik2qup"), + pubkey!("4FXiDN7mV3NteqUMrPJsZaUVpn6vsghnYdEnhYJstdyu"), + pubkey!("8RDJcd66btm6UxYozrzkQaVNo21CSsDHWzE7Buy3eyys"), + pubkey!("9XhUhyaaxNLpzy1ZSgAYkyoU4K2kcS7HBp72jSegPy43"), + pubkey!("3ucoQSjg6AVpSotpZRCoHV82v6A1hNyMe6kX8Ag36qG9"), + pubkey!("ECFuMRESmaWivsi7rha4rDmwuKeN8xppEophf6sNLgUx"), + pubkey!("BQbXD9tqv3ysrvojSZao2RoW9ucR4RmiKbKEaVWJp4J3"), + pubkey!("2jVdMx7fb88txbG6YoZzC7kT4Tq8rJDaWrNgbZ3ZnqCb"), + pubkey!("563zd49mogp48wBC4XEjzPQ2hZNViSTPjiN8QUteQjMv"), + pubkey!("Bw5zgt2ewuYDB63VtU5LDNNi8GLUwcEqE9iiuLwWUPyq"), + pubkey!("2naDt6dWy5uF3vbWKVxddTKmdEg3FaF1d89CBe3EaqYn"), + pubkey!("Cy8HEkWe4xqC62A9d4ZUzHS34CugT9SNLxgRazXYibW"), + pubkey!("Gdmd1M2LAYvU3A8R5imZHeNSguVNJDGNhprtyMDADrAq"), + pubkey!("7quEeAYjHqXsrR5MNRtsLKz3vfZL7CtoaLjpaFotqaFu"), + pubkey!("Ecu3AFZM8vHpEEfCDU7ecTMY8eMiXpPk1df3FrTTtR3m"), + pubkey!("Ak9DsM3BQg47CC8YgGbUiMSZgJyUxqjvckeKybeJd8gn"), + pubkey!("E1L58mNnUc9STrauT3TnR8Qb6EGfjqSmHBFNyP81ii1m"), + pubkey!("Enz7GA4a6hZ8UKtM4urov9HnH7PP5kkjs5Yggg7BYeZT"), + pubkey!("BREFixw21JPC1Vwad2tVCCUD4qrD79sKpKmbqgfkrvCz"), + pubkey!("FhEtsokybH9eqEEbreQqxwndUSYz6jvbiYjA75oX5faq"), + pubkey!("G9a3certFcZbWkrsvzq5E62CxgoBtEv5NFT1wjr48SAB"), + pubkey!("6BNk6UhhpaNE81MscSKnTVGnpUeptRyNPDRQxzD42LuH"), + pubkey!("7ry8P6VPABw58QcKjWi2ksgcziqdX5Woc4NubiQWXwBd"), + pubkey!("AjJk2eKmCUUXkcFQASGdcBxNh4UL2MuwQ9Bjdq8RQu5"), + pubkey!("DmzDHcbMFx2buuhWSHq7uk9345A6awrQg5dVLtSWXXa8"), + pubkey!("91wUgZtGXWJzQsFKq1T7xrgTaCYWLsLcwPko38V8FjUR"), + pubkey!("DVB69rna82Y67tn3Sgiy9eTjZmtwPppdsYv5RBLfQfJd"), + pubkey!("Hudzsqx4C99GEhZVzjzsjKZoF6kooUZdBDWTKJFnAcYk"), + pubkey!("F8GDWM1WPmnaRPJRpRuSm2WLfXLQFp2abiBorynpWmvR"), + pubkey!("CQh1EwRmscHSE8jna6aUxAkjvr2VwLK96GWQz3tbmKqA"), + pubkey!("6x6u8VZct4hrhbwUxyATaHNqCZytScFXGWkdwqG7oK3u"), + pubkey!("MadFFXJx5oqYzJyoRtMgDm8PwSc3BwJpvc5uaEiMDuo"), + pubkey!("582Q82NVuUespuZ5xwtJZnR9UuMcmXqmUgSysBSx2VtQ"), + pubkey!("GsPisQ2H2FLifx9T2FkMxXAxBaSCVX4qZNP1yTrS4DUA"), + pubkey!("EcVpECeotzrs2XGcrDeBiDg8FhN55g4aAgjKJja5QQXb"), + pubkey!("2eUFNjWuoSEQTkgvfHdrPcByXkwA7pTBiyZi95u46PJ1"), + pubkey!("4RGNSXy3QJTSeQgmhqKvvNhhkpD7KAb3rok2tWCSQ4zq"), + pubkey!("BobjP8dCMCQ4LeCqymDzHqDcyQNTGBBbo8gNG3N5Tk7t"), + pubkey!("BVKmb7UTRUoQriQMUVfpH8f9jn539Q154jGWzPVRCPQQ"), + pubkey!("VKPqFvbo4DuCTBCs4rvtGDUTdGEAS1R82R6Ep8KnzL8"), + pubkey!("JUskoxS2PTiaBpxfGaAPgf3cUNhdeYFGMKdL6mZKKfR"), + pubkey!("7seVHpqVocNhW93j6GCbX4BLHNoG17ug4s62TCsR4XyC"), + pubkey!("DeKg3DbqfV7bdmHzpkc5GY9R78zKWPLqZyn6sh2TYZmv"), + pubkey!("FLNQ4FHUAy8be2LJgggunjg1tAehVvt1eQDpxjmWVeFA"), + pubkey!("BmnChLJUQBWaHDpwurTtuNpqZkPf6eMNbgxP5Am2pL62"), + pubkey!("2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf"), + pubkey!("6fFWA9BLyqNSSRcKvdyGtLrgaTzx31fs2oLaQnrSw3Vr"), + pubkey!("DbswyzwwS1LRYKSxvwJbyE8SVxUYurfWJEu1mUdXytjQ"), + pubkey!("6KW3smHmstPUhAbmq9CohiwbXmWcq81jfRehEE7xQ4ia"), + pubkey!("Bwmo2vpL8QB9PStEbdg4UZhyUwA7q5HHhyJpPVa43Aeq"), + pubkey!("CPYRPM421AaXBM5qGShnWsoiZMVcrCCS3JYfPG7SYYAK"), + pubkey!("3KFstu1jDPb4TByc9r4915kYkghkUNAyzQsASQGuPZWD"), + pubkey!("884aD4A8GyFBxqmuP6QQ1Gi2EwucFykwXFVkdom6uhhu"), + pubkey!("CniJsr5gxFxhL35FVWFtYtutajKjn3wYQt33puTNADTc"), + pubkey!("BixZzjYNyYGi1ywdHHekRCkxcQwNvtCKxxGppNCecYbT"), + pubkey!("387EhfcBR7Tj662V1PrzhbpaHPab4YddAPTKPf21wwkk"), + pubkey!("6jr135RZTwHfTmuxSTbZvoL7TQm1Qd6Koqfu28DaE7XN"), + pubkey!("8PwomUFs26C74SCMF2S4cMx1aujZ3i4WCihE19Ztm7n4"), + pubkey!("3bNurM55JJyHTMYfMJw27gLSHKknthBkhSazAi9WX2or"), + pubkey!("5eefJGcPYCqGWDzUEMpBwRFyE8DfvqwqDk9goFjXGkEn"), + pubkey!("4r2wk9KM1wjrUhmdUpv9SvuFUDwfpRUP8ezxn22j7eXX"), + pubkey!("BVtYZDPpsrVTnwRFNmGDbg64uToGS3mxxY9U5MAZ3Nsw"), + pubkey!("DSZFvyL8vwUZ8i67koaJT6nA26t2VhWftqCrYp2sxbQr"), + pubkey!("RKm9CQxvwi9iN331Rkwktm6achj4JW5pFYsKXb4xfeG"), + pubkey!("2zahht8VNqDoUSgr4gyUXXzkr8xziyD7aTBNi6i9oAHb"), + pubkey!("DubBYMVsQeQdFNMR3ho3fDAQxqSM2SVNc9HsQXnzjJ3F"), + pubkey!("5onZdAogWbxCzebwvfeidNeX9VgkQzbGpDuwS1THkhuV"), + pubkey!("2xTu2WHcvt5VwbdwBr1MkfV72jyy9phd5ksfrGy3tyma"), + pubkey!("7ceTUBq2k8pNjrcjyWHapmhP51J1ttyKUHWk1fcqh176"), + pubkey!("yVqjGGK34PgKwhEaeyp4UTLcQiJQaKTPrnbJADaMj9x"), + pubkey!("2mAShJV8RduZEUMvYWDAZgttRzWp2knc5EHoVszuVoFo"), + pubkey!("6Z6PsSvbQHndjqNTFZUEYLpouFcExYxaceCHAyHGNgMc"), + pubkey!("FTX9nCXBBczYYtFxi1pus76YQt7XupYx4KVY36FoFepT"), + pubkey!("3SK1R1wWABFtCBRpyCUe7vshx2vew4m52rCz5V5Ynt1F"), + pubkey!("BQiMrFT8Kcey131zfk7jKwvS4esvFGmLreNPP6SfoF4R"), + pubkey!("5ytZTkw2noiFm6Zu6pZcQ2Yeg1EBnRnxY4dKpNpYF4qL"), + pubkey!("7qCM9LFQyW49TX7Dp8GaqQ8fytAEEDs2CJ2Td1jLtzXf"), + pubkey!("9ZFLHxu35yZaB1syp7L384PpvtgUX525M38RrA3CM9bh"), + pubkey!("EVrX3x3zfjKjY2baajZGUTwrgbKFkw1iQP7FKAEuRLF4"), + pubkey!("BRa2oSPwuTebwsc28jbtMxsYtuvqbPoJkVM7n9q7zd5o"), + pubkey!("3B4nPAvYGo2iXxvAHRktcFBexAiU2wkqFaywK4wtM1dn"), + pubkey!("6yQQZhoNDd4qLwFn4LcQuH5XEdCRQQgy9o15DMLot4pa"), + pubkey!("CD9QsVyyWvmUnuZeJJhQ2VzWAYvkKiRpCobpJWFv2YkD"), + pubkey!("D1w6h4EqQZw2RYped6Y3RV2jDeF2a4TFkNoySbNi7j9j"), + pubkey!("9KRVJDMDAFHsQ3FD6QXsmQAEf8XAXgFHVWeQcStBrtME"), + pubkey!("73FhRUQPTQrsyVMjLCx64ibqUD1egynyhzrcL3qcW2gi"), + pubkey!("Fj2HgotRPACCTiLK3qXPtrx9ez6EDgVpUKn9NQUa1LRu"), + pubkey!("GpwDBLw12TCKMQkqJEGHppKcV1ULBPfXZV4RFwWftuxY"), + pubkey!("BRgdUnFLFKFNR4WF9eWAesez67D7mU9mri1pjd7sj8P8"), + pubkey!("Gv3mxjsWGH9FFvwdqE5YiihNSGeMaNY4V1A9xxfRtwWB"), + pubkey!("AN3tqhYC8TkugoyFzmMCTwnEaNpDDyTSxBjpvZrpDjuN"), + pubkey!("8fTkqpcqUhAs6KbrbkrZcMLKvrf5WymWizRi7v2TFqBY"), + pubkey!("3nk9Bz8anhjZrRvHd52ZwG3JLasHUFKU1xbo4vB5XekX"), + pubkey!("E31Z3uXrzEFMxscd171QH9aCJZiMWLCGYbVgwcHx2G2z"), + pubkey!("D9FyQGZcmJPEv4gAD7oH8BTpj4VQiwFEKyCvcQMgYAY8"), + pubkey!("CzQYL5eL2ZQH28yvk7brWotjj7TrXaw55nasGnVYyLWn"), + pubkey!("aa1AhZSe87ordRejNkGLaJN8vs71t8WDD3aYfZNT7cf"), + pubkey!("5Z9UpPrFQTeKeJeKrgEWYcSG8MFN3YtAtdWw7JLhLJHL"), + pubkey!("85j4tHFBabkGEnoGPj5eGuYz3oUtqgZcHkft6aYkt1er"), + pubkey!("AeeDSNJicCbwTqDfbF83TA4MFaYP1dZujyTTQm1bdQEe"), + pubkey!("xrykqd9rb64yNZeoxzHLAGFPrFgkN65w3aEJ6KqzN74"), + pubkey!("AkifiiLputLYjN9BdWvQF3ug2odhe2GJBUXmdGVENKsd"), + pubkey!("6jy3U836vS8omfhYt4nEzQyyXdBYzQSiDskSJTUTS71n"), + pubkey!("GRssGjrNG7XtmZZK644VTN8BkA4UEWSmH2cke2Zj6prf"), + pubkey!("2ww2LiHU2hdCCiTQbvQ72PEgUBrznpmLpTUXCBJZzLPa"), + pubkey!("4o8R93WLNpj6kwc8ka8ptLW7dShb2hoi4S3dFLqRyz3E"), + pubkey!("EA7jgT8r5K4r8iFhxhGxRQYYaQK1RoYKyZysaNm5r8ZW"), + pubkey!("Gi2JbovBxEgRyUrf8tg1VE3UZqdQ5fHB4csUKG85cTA4"), + pubkey!("7YMFFjzKFAZVauacGPvtxuVngTbxPsv6EM4oiqjxMYtz"), + pubkey!("CSVmNyCwaMtJbvYCH7JbN7GXF5TABrdTUgxy4VpHQ8Kb"), + pubkey!("2jWN7kds7jBYmHr4fCXu1gMJUkgqJCJoVwJ1Rv996jSS"), + pubkey!("663A19n8DaCXdaSycFwmTxXeNBKFMZunoPRoy6LDbK6k"), + pubkey!("E3onGu8Fe2bd3pCpFaXmApaUB3JuB9neCqx6YTh3Lo6R"), + pubkey!("E4DwabawCYTZwqNv5LVj3LHZzPP8XL6xuya2C6mkJMwZ"), + pubkey!("5YLUmcpRKyY1YQESMTFiwoA8moTGcjSLadoRC9PxgC5P"), + pubkey!("2RiqNWDWgJnrX2G3ahBJYmt3ab56K5HXFzT7WGWf7h4X"), + pubkey!("HAXeS5472XnL58J5Wq83qc1rZqc2Dud7Yo44A6aMakDs"), + pubkey!("8LNCzYSVijuFDxHH1K62B5jo72W7gxicB3QURBfHkMbj"), + pubkey!("5MRLosQFPcmqL2DNBLKmW5dmmyXF9jjx7sA8QK8Btd7Z"), + pubkey!("13V7kcaGMsXHTbZNFStFySXA2yoLAupHXBkPG61AV3F2"), + pubkey!("EcSUyx1axTPa7CtbiNiKo8c9YTmSigLyPG9mLBRmfts2"), + pubkey!("5qrvvuCB5Lo75tdyD2kRaGASDanZh9b6EX4VSWWmuXEf"), + pubkey!("58uP5UWXaZVv19uoGX4MqqRS2L6bA7tGmxxZK51QXFKL"), + pubkey!("6EFuKUZEaUCGHsmei3NEJhgDX3458YTkEfQboGNj5x6n"), + pubkey!("75NTaCnZU42fsjqEdr8gKbmx9JHUXeeXzXFA7HG8JNea"), + pubkey!("5TkzZbno1x5t5MBEoBDXdicckAoMCABnWVLS2ntbk6hF"), + pubkey!("CK8ei4KVemer7GznCQK9eu3BRQMBLXXieMAbJAeHMcoN"), + pubkey!("E9sTbkJ4Sxcvmy774auV7wxh6vKt9TXV666z4WR4NybT"), + pubkey!("E79uaVcz8KpQUSNVA2JGX5rqM6DuWm3KZThNvBoqGRFn"), + pubkey!("93skKa6mstJdK9LF1UDRVEv9QVpD8738JpfTtHmxBTzx"), + pubkey!("CiBaXEQxvStqkSEGAYPX6YdCnU4JzndLgFNGh1Fr4Pd"), + pubkey!("Eu3KDfvMU4PXSqNHFU4MFVLfR3BDwm3cdNqMgEwvqpiN"), + pubkey!("5fAtP5JMbMjVuz3Dt4XbnjdvQQaXKopuuJqEkDYumy6o"), + pubkey!("6Eqy9tnGg2RMqwM8i1NXCDj37EfAzniKNd87jWA6Nhou"), + pubkey!("DgXUUwsEw88fbDi7FmASgJfDR1mFYJnUBSpT46aBtp35"), + pubkey!("GkPpc1auG5FPgqQXYytEUUndZstZESh3h3bPPJ9jyZ4b"), + pubkey!("8fXvsNFwihfyKRDEv4oAhM2E5NWSNBMByKitZHpUD7Tj"), + pubkey!("CX47idzWhK8cDLpUNYraPFoieGxy8wzR9XJF3Avsq6nF"), + pubkey!("HoeivdxHbL7pFY7CyhZP9G8eBHouRw3ckkSnekj7Zh6u"), + pubkey!("69m9TPRfb2oYdquNDKu7XRfSaYyKqv9D2MamcyWSeGj7"), + pubkey!("8525tNnMAxPLqNX3t8EqgRzhKvUQo6oUV8ovUnLWPWD9"), + pubkey!("FzfTigLWBhVzmStXoU91tBoWXjKqqyEMLzPMuwm8xj53"), + pubkey!("5rUNTDMAFDdvSQEHSpNNtUVRo5aKdPfLRsKsWm9t4Lv7"), + pubkey!("FKSNp1wnJMabfTPJBD4M2WVPTMwJoeJ7gDiGu1ZW6nFe"), + pubkey!("8TaGL4S4vx3tyZcnr9fV6A999MfCsdC14QLAB2F9eVnm"), + pubkey!("UuGEwN9aeh676ufphbavfssWVxH7BJCqacq1RYhco8e"), + pubkey!("HW252Pdt9qsyakaBepVWtcEKesMLJKCnYJy9MmZNtng1"), + pubkey!("5YLjGCCdStdL1WfAmeSUU6P2X71Rkc1poxKidcbA3KK5"), + pubkey!("AwScekxPFJchPcWkwuKMb8aK8fjEw5o7BGTM7eoofk9A"), + pubkey!("Hzs2oCTDfEXQc28wMzMujKpDYMMotYaRkdQdtHZNjh3H"), + pubkey!("H7QppzedhVRWbG4vGRVGSPP8pcmBFqDKC31xTEjLnQHA"), + pubkey!("GjmLjF2vhmLgbEy5VgEZz7gWEzyCqZAXMBsKBsSk5ev4"), + pubkey!("HzJr3T2qHesCVkQZcQJK8nZWkns1N9qj4Hj4dHi8ZNP8"), + pubkey!("BQQYZKHfNhmUrdU2UhwZzWdVpiVzdBCa3qLerhDrcbAs"), + pubkey!("7LfVG4MNxTenrhw4xczt3ToFL6jd3KUXSzKBMpxambtM"), + pubkey!("DcBJoxBb8KNqrwdbMKFSR8qjyYYpTy1Pu7vU5K7LZWNn"), + pubkey!("DGs8ZrfatMpLza4ekXDdq5mmEmt5QqeKQ1q3WrpsrM3s"), + pubkey!("4DGxxu1fTbteKXm6USy3enW7S18iPFuJkqhKrSopGeBS"), + pubkey!("AkmZXNFjEL6LgxGi1eM81iTXfwNJgeFG7iXUiFfmHni8"), + pubkey!("297ogus15jvgePqXZCwT8nB1gvwgCJYdKcuXKiyH4TfS"), + pubkey!("3yKHWBKD5DeX7vG2ESJwWQWF4HgmgHumeXPhaZnqiore"), + pubkey!("4AQBsVmECmSBPh4JicNhGaT9waHETNxeNkaz72tezgSR"), + pubkey!("6zWbGFC9WgPymyrTFM2MKAwLu8vKXwZJjFUcksikdabE"), + pubkey!("Xp7Swytm55aTD8onDegFAVm4gC7zdCCkYMobRg5oHfr"), + pubkey!("3rB1eaJeFYKHwRP1q9mAVEYxY2cCJkHx98yVppB6tPuu"), + pubkey!("ABcnheNNaj3q3Yi1pbDrEhSjKJYqJcn4RY4tZcurdXYz"), + pubkey!("CwkzrFZmFPqX1x52uqRV3d1JncgmMiCKTxScJ3XSVA4A"), + pubkey!("3t9CReK1B7z1B4sTnfWrDZtyGaX5pV6ydXvE1vanJ4FC"), + pubkey!("G8d3gRGvAg8k9oiazKPPAA49BgAJuPACFbdf5CRWjpaZ"), + pubkey!("gpoo1atPkrKnfxQ4Qt214ErbgBBJeiksL1EjqBHynbo"), + pubkey!("CFRt6hxJoYQYgS9jVZHQEYMpEzfRLxAo1vwq7R8PnLJ7"), + pubkey!("D4b7ocAwoUrCM7pZZYfqyAftHXDVhNwbw9JmnrAHG6z6"), + pubkey!("BntbD7PCAXPhXANT2XibNgduJS1UL2dysUEPMd9gLeJy"), + pubkey!("3rcwuJQTBG9D5d6P2TurtQQTaTVkw4HPufWGCdhcGqfm"), + pubkey!("BLtavXya3V2o9BvREamtdn2tgG3o2tGp2qpKr2VbavWg"), + pubkey!("91rV3hD3ZfMC1smebh6nix4mSUePXLewsxj1Ncz79LD2"), + pubkey!("6MRghTVnMEXiubLs6V8s3bzz2FEke5UugwR7ByuioB9H"), + pubkey!("7HZ222sahftEBj8JdyHgvy1oGQTMYjtdNSUZDqzfNyiD"), + pubkey!("G5SXciAG8aacJ5F4nRAPzDXfn3Rjfbwn1EMvEccrFnjZ"), + pubkey!("EoB4LgmryBX1m5YmZe8wohQzFssjMhEK1DWwcnk5Gmo9"), + pubkey!("GtCneCahPq4m88zQxKVKnZM72qdbSUEDp82UDu8iNLWE"), + pubkey!("HfuUoJHkdxnXb3wCG4roMkeBA8nX4XpqFm9VDwtNQtT9"), + pubkey!("GbD6i4SxcD2Sqae3wCPvvHpNtr9LxahtHHC8wfThmr95"), + pubkey!("G4WwpzfCXPzAJ2jfLXZRk7gNTEfaD8jNss5Xij33N21e"), + pubkey!("Bo4nGugF3usS4N797H4LEm2r2d794kvht6HruwNCVe7Z"), + pubkey!("2V1pTma3ZcctFvT1tALnoc2W2u5W1DmDqi3BjqerHrCN"), + pubkey!("BX2X2QYU3twF4bRX2Pro4ARUNXi9cDd7cpRdZFW9JWC8"), + pubkey!("DF4Ad2CRWyR5KMgGaqcfG258twr9LmsVc7hSCk8Pizfb"), + pubkey!("AonibGzhwQ2MTtYNXiaEPnrfQ2c1eqJwp8SDfZL46TL3"), + pubkey!("5o6PCwxNYpoa6wdtvaTiYyKrE9FoNE1bT542ZTuNgJpr"), + pubkey!("AcVvuR5PoA1Mq5W9UY9x6fEAJthK9a1R4QHWQmETDb8h"), + pubkey!("367Hqa8Q1DY1p5jKEmpCcJiyj5fH1dfbD7ZUBhCBbEa7"), + pubkey!("GmRC6EhKtBEcKM1bjCBzamWDy3qmP5z2Kc9tYjQuc2Pn"), + pubkey!("4iZPMVzyGGTnF3hA45vrSFP32tZ7yKiyQd7MiJHv8dyF"), + pubkey!("3yxPRUpxUL2hLMLzDTWQpAFqY82MCphgy6iik3J8ZDLr"), + pubkey!("31KPvwdWKK9FVPAER5fXLHfCCBQ5wGyUSqFMnpF9Xvyj"), + pubkey!("4KVqRjx2Dyo53wANTGh5QKQQbatqUj3wfP9ZV7YinJTP"), + pubkey!("EEPoEVgsabibKaAxs21JPBCMqKHmDcWkWzvXjS8vPf6L"), + pubkey!("9xUqwnwaHnmXJifpJbu2dYu2PybVE2RDZhB7SHhLU2tL"), + pubkey!("27R5t6DAWFnMXP3ZHA4aXabtxZ2nP4qH3BVQN1oEWSkj"), + pubkey!("4hewNZbUaUziPGQ1yTmSX2yx9syzkc664RNjfFFS9sK4"), + pubkey!("EpkrcNkBaK5eLwMKpV52H5MjPxcdWxnb1FcBr9FUEmMt"), + pubkey!("6pKuMtqh56yaWT6ToYk3F7WcZnowAv2DPuptkYq9pPKc"), + pubkey!("2icoVmEXH2q4zMmHChnFPS6iDt3hNTPrz3pdEEeK3Dqo"), + pubkey!("gjwc2FafGF46Yn8aSnQu3a9S5MN1knwkc99yTzyqHnR"), + pubkey!("A4ke6mJAL7muUJf4QfUV8yLMr2A3nH5Qnu1shV1DefvV"), + pubkey!("CpF8aa81tZ6uAEoHHK5N79SREL49JyZxjMLPssY6qqU1"), + pubkey!("8ujTq7pjihTBbebirvHohqykFLvNLa37MLTtFQcp9QHa"), + pubkey!("3rXinbzxFTQ8uJTEDbU4XUD4gYBaRkfg6DDqpEZKwepf"), + pubkey!("9zKXwnQU4F2yr2NdAAfzu6XGJQCWmgM5fMyn9qsRVyhj"), + pubkey!("AVYG9UHetNHT1FEDPLv9pN2sCCH4CLsjvkzGjVXBfEiS"), + pubkey!("82dYc4N5KJvMyAiSRC74D3uHyNGy8Wr4ghfer15YLQa6"), + pubkey!("GEwUNpFEN4q4i8RBxmjQHtLC57tmQkUVDhFrDiLjxv5P"), + pubkey!("B8esH7ZNMHVwm3gQuzeM3XEa8pUa1ELRCYTezwRsMcjA"), + pubkey!("6Mn9Th41tmNqXyPS8y6hno2EB6wzHcYkfnh6br3NGNAy"), + pubkey!("ECHb13JeXfPdj5Z3EEFQ1vcGEpagqHXrWGZF7NdaWF4z"), + pubkey!("FBsBn9iScLsvSe9oUtQiXuyXGh2uJZfwAAzzD8gP84AU"), ];