diff --git a/Cargo.lock b/Cargo.lock index c1f4937..8d229be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,9 +76,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom 0.2.11", @@ -147,6 +147,20 @@ version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca87830a3e3fb156dc96cfbd31cb620265dd053be734723f22b760d6cc3c3051" +[[package]] +name = "aquamarine" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da02abba9f9063d786eab1509833ebb2fac0f966862ca59439c76b9c566760" +dependencies = [ + "include_dir", + "itertools", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-bn254" version = "0.4.0" @@ -264,6 +278,12 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "array-const-fn-init" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bcb85e548c05d407fa6faff46b750ba287714ef32afc0f5e15b4641ffd6affb" + [[package]] name = "arrayref" version = "0.3.7" @@ -363,9 +383,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.76" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", @@ -445,9 +465,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] @@ -461,6 +481,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "blake3" version = "1.5.0" @@ -520,6 +549,16 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "borsh" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe5b10e214954177fb1dc9fbd20a1a2608fe99e6c832033bdc7cea287a20d77" +dependencies = [ + "borsh-derive 1.5.0", + "cfg_aliases", +] + [[package]] name = "borsh-derive" version = "0.9.3" @@ -546,6 +585,20 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "borsh-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a8646f94ab393e43e8b35a2558b1624bed28b97ee09c5d15456e3c9463f46d" +dependencies = [ + "once_cell", + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.48", + "syn_derive", +] + [[package]] name = "borsh-derive-internal" version = "0.9.3" @@ -617,15 +670,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" -[[package]] -name = "bs58" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" -dependencies = [ - "tinyvec", -] - [[package]] name = "bs64" version = "0.1.2" @@ -730,6 +774,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chrono" version = "0.4.31" @@ -858,6 +908,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "const-crypto" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c63acd992239f1877a7d6387038ca886a2029e41f3d91087fd454f9995f22c" +dependencies = [ + "keccak-const", + "sha2-const-stable", +] + [[package]] name = "const-oid" version = "0.7.1" @@ -1030,12 +1090,15 @@ dependencies = [ [[package]] name = "dashmap" -version = "4.0.2" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "num_cpus", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", "rayon", ] @@ -1106,6 +1169,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.9.0" @@ -1169,6 +1238,50 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "drillx" +version = "0.2.0" +source = "git+https://github.com/regolith-labs/drillx?branch=master#ce0330957a2153c9191bdbcbcf4acce031ccd6bf" +dependencies = [ + "blake3", + "equix", + "solana-program", + "strum 0.26.2", +] + +[[package]] +name = "dynasm" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33dc03612f42465a8ed7f5e354bc2b79ba54cedefa81d5bd3a064f1835adaba8" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dynasmrt" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7dccc31a678058996aef614f6bd418ced384da70f284e83e2b7bf29b27b6a28" +dependencies = [ + "byteorder", + "dynasm", + "fnv", + "memmap2", +] + [[package]] name = "eager" version = "0.1.0" @@ -1245,18 +1358,18 @@ dependencies = [ [[package]] name = "enum-iterator" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7add3873b5dd076766ee79c8e406ad1a472c385476b9e38849f8eec24f1be689" +checksum = "9fd242f399be1da0a5354aa462d57b4ab2b4ee0683cc552f7c007d2d12d36e94" dependencies = [ "enum-iterator-derive", ] [[package]] name = "enum-iterator-derive" -version = "1.2.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" +checksum = "c19cbb53d33b57ac4df1f0af6b92c38c107cded663c4aea9fae1189dcfc17cf5" dependencies = [ "proc-macro2", "quote", @@ -1295,6 +1408,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "equix" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e25ed202554ac3bf3c8e5fab352947cc9b5c592e219185351d50fedf2b4213a" +dependencies = [ + "arrayvec", + "hashx", + "num-traits", + "thiserror", + "visibility", +] + [[package]] name = "errno" version = "0.3.8" @@ -1335,6 +1461,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fixed-capacity-vec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b31a14f5ee08ed1a40e1252b35af18bed062e3f39b69aab34decde36bc43e40" + [[package]] name = "flate2" version = "1.0.28" @@ -1345,6 +1477,15 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1361,13 +1502,10 @@ dependencies = [ ] [[package]] -name = "fs-err" -version = "2.11.0" +name = "fragile" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" -dependencies = [ - "autocfg", -] +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "futures" @@ -1574,7 +1712,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", ] [[package]] @@ -1583,6 +1721,21 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +[[package]] +name = "hashx" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49d1afec2e9689360a16d9790ac9704c038da4fd04f17890a9ff1c56e536e20a" +dependencies = [ + "arrayvec", + "blake2", + "dynasmrt", + "fixed-capacity-vec", + "hex", + "rand_core 0.6.4", + "thiserror", +] + [[package]] name = "heck" version = "0.4.1" @@ -1604,6 +1757,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "histogram" version = "0.6.9" @@ -1773,6 +1932,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "index_list" version = "0.2.11" @@ -1853,9 +2031,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -1884,6 +2062,12 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + [[package]] name = "lazy_static" version = "1.4.0" @@ -1892,9 +2076,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libsecp256k1" @@ -2084,6 +2268,33 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mockall" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "modular-bitfield" version = "0.11.2" @@ -2141,6 +2352,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "num" version = "0.2.1" @@ -2244,9 +2461,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -2297,7 +2514,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 2.0.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 2.0.48", @@ -2365,24 +2582,40 @@ dependencies = [ ] [[package]] -name = "ore-program" -version = "1.2.0" +name = "ore-api" +version = "2.0.0" dependencies = [ - "bs58 0.5.0", - "bs64", + "array-const-fn-init", "bytemuck", + "const-crypto", + "drillx", "mpl-token-metadata", "num_enum 0.7.2", - "rand 0.8.5", "shank", "solana-program", + "spl-associated-token-account", + "spl-token", + "static_assertions", + "thiserror", + "utils", +] + +[[package]] +name = "ore-program" +version = "2.0.0" +dependencies = [ + "bs64", + "drillx", + "mpl-token-metadata", + "ore-api", + "rand 0.8.5", + "solana-program", "solana-program-test", "solana-sdk", "spl-associated-token-account", "spl-token", - "static_assertions", - "thiserror", "tokio", + "utils", ] [[package]] @@ -2570,6 +2803,36 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "predicates" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" +dependencies = [ + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -2591,12 +2854,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_datetime", - "toml_edit 0.20.2", + "toml_edit 0.21.1", ] [[package]] @@ -2791,9 +3053,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -2801,9 +3063,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -2832,9 +3094,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -2844,9 +3106,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -2987,7 +3249,7 @@ version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -2996,9 +3258,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.7", @@ -3128,33 +3390,42 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "seqlock" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5c67b6f14ecc5b86c66fa63d76b5092352678545a8a3cdae80aef5128371910" +dependencies = [ + "parking_lot", +] [[package]] name = "serde" -version = "1.0.193" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb1879ea93538b78549031e2d54da3e901fd7e75f2e4dc758d760937b123d10" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" dependencies = [ "proc-macro2", "quote", @@ -3163,9 +3434,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -3241,6 +3512,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + [[package]] name = "sha3" version = "0.9.1" @@ -3366,9 +3643,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -3382,14 +3659,14 @@ dependencies = [ [[package]] name = "solana-account-decoder" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21ed570fba6f909f69c888b48b39c7e61b454e3594e448d0dad9d973f27f5668" +checksum = "142161f13c328e7807fe98fb8f6eaaa5045a8eaf4492414aa81254870c4fc8a0" dependencies = [ "Inflector", "base64 0.21.7", "bincode", - "bs58 0.4.0", + "bs58", "bv", "lazy_static", "serde", @@ -3407,9 +3684,9 @@ dependencies = [ [[package]] name = "solana-accounts-db" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c457b335c3b13b0df99ffee59cf8b3d92e861abbddd0de93993367f449a76c" +checksum = "5e8b4b15e353d5f0e0ddd77966c6f01b296bd83569af455da5fd9329356ff642" dependencies = [ "arrayref", "bincode", @@ -3422,7 +3699,6 @@ dependencies = [ "dashmap", "flate2", "fnv", - "fs-err", "im", "index_list", "itertools", @@ -3431,10 +3707,10 @@ dependencies = [ "lz4", "memmap2", "modular-bitfield", - "num-derive 0.3.3", + "num-derive 0.4.1", "num-traits", "num_cpus", - "num_enum 0.6.1", + "num_enum 0.7.2", "ouroboros", "percentage", "qualifier_attr", @@ -3442,14 +3718,17 @@ dependencies = [ "rayon", "regex", "rustc_version", + "seqlock", "serde", "serde_derive", + "smallvec", "solana-bucket-map", "solana-config-program", "solana-frozen-abi", "solana-frozen-abi-macro", "solana-measure", "solana-metrics", + "solana-nohash-hasher", "solana-program-runtime", "solana-rayon-threadlimit", "solana-sdk", @@ -3457,8 +3736,8 @@ dependencies = [ "solana-system-program", "solana-vote-program", "static_assertions", - "strum", - "strum_macros", + "strum 0.24.1", + "strum_macros 0.24.3", "tar", "tempfile", "thiserror", @@ -3466,14 +3745,14 @@ dependencies = [ [[package]] name = "solana-address-lookup-table-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba35ca5c434b2479a2a55b831461bd8cfdf2c389ee3ae4a0fc51918fbe17d88" +checksum = "4c4eef9fc8aa3ff804dbf17766ab2d2fe38561adc8b521705faa782c18a108d8" dependencies = [ "bincode", "bytemuck", "log", - "num-derive 0.3.3", + "num-derive 0.4.1", "num-traits", "rustc_version", "serde", @@ -3487,11 +3766,11 @@ dependencies = [ [[package]] name = "solana-banks-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f22c7a0b5d1a81193875dded16ed189666fab0b753d46faec311c2798e0af34" +checksum = "13a4cbe27e78987b706caf90cbd16da9da3955c09a660b8107a96c2cb32f1124" dependencies = [ - "borsh 0.10.3", + "borsh 1.5.0", "futures", "solana-banks-interface", "solana-program", @@ -3504,9 +3783,9 @@ dependencies = [ [[package]] name = "solana-banks-interface" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2652008dcd55d163e08e4d94c2b7592cb8562b33e168ac86462ddac4dca143" +checksum = "741279a09bf5ea1a3d17e591db7b189e163722e5c46423308c6a6165bea5e74d" dependencies = [ "serde", "solana-sdk", @@ -3515,9 +3794,9 @@ dependencies = [ [[package]] name = "solana-banks-server" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a01440c39a08f90f8016013918491f8ffe16d066efe0508f9cb12e2863f6eaf" +checksum = "f66768544951feb91c3470e255d4613295b5cc5a58a9cc6a4207ab9a0178cfe9" dependencies = [ "bincode", "crossbeam-channel", @@ -3535,9 +3814,9 @@ dependencies = [ [[package]] name = "solana-bpf-loader-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571e8ef9d82bec9d32dd2d54b00e1572a85c967ed996cf737f3a52946d760623" +checksum = "60e9dd5e42193260cca0794bf4ab9e248f44b3d9710041f241b130d26ed682bc" dependencies = [ "bincode", "byteorder", @@ -3554,16 +3833,16 @@ dependencies = [ [[package]] name = "solana-bucket-map" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109fdb52669846283bc6ef2ed87c832295af6f7e3c4e7888127b3d506054d651" +checksum = "1a7b34296d69867253671a71a2231b8d5b4a810bd7a5c1c603e7b542832d5980" dependencies = [ "bv", "bytemuck", "log", "memmap2", "modular-bitfield", - "num_enum 0.6.1", + "num_enum 0.7.2", "rand 0.8.5", "solana-measure", "solana-sdk", @@ -3572,9 +3851,9 @@ dependencies = [ [[package]] name = "solana-clap-utils" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4729fec3c2ac37b7daaf24c1ef879bbedbff3495b1ac728d9b627282d878753" +checksum = "e8e9f61034a61db538a41700b6df0b4b9f0392038adaf780150481923ff94356" dependencies = [ "chrono", "clap 2.34.0", @@ -3589,9 +3868,9 @@ dependencies = [ [[package]] name = "solana-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da13019a833940af2edebda969db4337ab11c6fb220eb0d4c02d79c83ae8034" +checksum = "13f2bd5a986d7cac1b4ffb4344413b70b6f21fd7ffa92a985911756b4ac7682a" dependencies = [ "async-trait", "bincode", @@ -3622,9 +3901,9 @@ dependencies = [ [[package]] name = "solana-compute-budget-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba64641b22efb6332088dc5892369a2f2049f83e66459ea300a4fc74a7a9f84" +checksum = "ca100b2bdd7e455f5f0b9791bc204dacd684a0373ad1032697dbad43f34e527f" dependencies = [ "solana-program-runtime", "solana-sdk", @@ -3632,9 +3911,9 @@ dependencies = [ [[package]] name = "solana-config-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04b91ca968a63946e7513a1de20188e6e917f09136339ee3bec247aa0e985d36" +checksum = "970d28779e92a11e32a89ee453edc7d89394d3a68d8c4b75ef0ffb833944c588" dependencies = [ "bincode", "chrono", @@ -3646,9 +3925,9 @@ dependencies = [ [[package]] name = "solana-connection-cache" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a850c0122f094efb83df00ab080ab6ace0dcd8dbf91240f91832157ee6d460" +checksum = "dd7d0022ded19dca32ced5528c6a050596877fc8b9a89322d876960a89466e1b" dependencies = [ "async-trait", "bincode", @@ -3668,9 +3947,9 @@ dependencies = [ [[package]] name = "solana-cost-model" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c6e08e5be41ab19c7906a6b6adf58172fd49ee042f8511a6c4e0155daa1b7c" +checksum = "dd3c63699df1680535daee8e486bd496e2ec849c427de4b6a42d4f1b27430949" dependencies = [ "lazy_static", "log", @@ -3692,17 +3971,13 @@ dependencies = [ [[package]] name = "solana-frozen-abi" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2c5e5dde22cac045d29675b3fefa84817e1f63b0b911d094c599e80c0c07d9" +checksum = "35a0b24cc4d0ebd5fd45d6bd47bed3790f8a75ade67af8ff24a3d719a8bc93bc" dependencies = [ - "ahash 0.8.6", - "blake3", "block-buffer 0.10.4", - "bs58 0.4.0", + "bs58", "bv", - "byteorder", - "cc", "either", "generic-array", "im", @@ -3713,7 +3988,6 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", - "serde_json", "sha2 0.10.8", "solana-frozen-abi-macro", "subtle", @@ -3722,9 +3996,9 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "296e4cf0e2479e4c21afe4d17e32526f71f1bcd93b1c7c660900bc3e4233447a" +checksum = "51600f4066d3663ab2981fd24e77a8c2e65f5d20ea71b550b853ca9ae40eee7f" dependencies = [ "proc-macro2", "quote", @@ -3734,9 +4008,9 @@ dependencies = [ [[package]] name = "solana-loader-v4-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a902445f0bdf610e7eec94dab7f4a8e756d001c17651647730f1efcb9d7af6fe" +checksum = "9c566ebf0da216efc70054bf2d6d06c16fe44b63402c6f3bb04f6a88d8571d9b" dependencies = [ "log", "solana-measure", @@ -3747,9 +4021,9 @@ dependencies = [ [[package]] name = "solana-logger" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37a1b1a383a01039afbc6447a1712fb2a1a73a5ba8916762e693e8e492fabf3" +checksum = "dd79ef26804612173c95be8da84df3128d648173cf1f746de8f183ec8dbedd92" dependencies = [ "env_logger", "lazy_static", @@ -3758,9 +4032,9 @@ dependencies = [ [[package]] name = "solana-measure" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19831a93d760205f5c3e20d05a37b0e533caa1889e48041648ad0859e68ec336" +checksum = "300f716a5f1c2f4b562fb008a0cc7d7c0d889cff802a7f8177fdf28772ae1ed9" dependencies = [ "log", "solana-sdk", @@ -3768,9 +4042,9 @@ dependencies = [ [[package]] name = "solana-metrics" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63c23a8db755b2903262ad473e32cbf0093e2d3a0a7b8183d797a182c08326a" +checksum = "abf1705d52e4f123856725e1b3842cd4928b954ff62391a95af142a5adc58ac6" dependencies = [ "crossbeam-channel", "gethostname", @@ -3783,9 +4057,9 @@ dependencies = [ [[package]] name = "solana-net-utils" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ac1afc7feb590b45fd72bee0ca4c4f24b2386184d7e00d9f0d17913655bb4a" +checksum = "b1f2634fd50743e2ca075e663e07b0bd5c2f94db0ac320ce5bc2022e0002d82d" dependencies = [ "bincode", "clap 3.2.25", @@ -3804,12 +4078,18 @@ dependencies = [ ] [[package]] -name = "solana-perf" -version = "1.17.14" +name = "solana-nohash-hasher" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdf5a429e018e8ba693f4c43f833192db421fe97b88dfaf97041aa258e4b191" +checksum = "8b8a731ed60e89177c8a7ab05fe0f1511cedd3e70e773f288f9de33a9cfdc21e" + +[[package]] +name = "solana-perf" +version = "1.18.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0264d7093d44c239d9eb41beb6877b7b1eea5ad8809c93c1d9ab0c840ba390" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "bincode", "bv", "caps", @@ -3834,9 +4114,9 @@ dependencies = [ [[package]] name = "solana-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a3b9623f09e2c480b4e129c92d7a036f8614fd0fc7519791bd44e64061ce8" +checksum = "2a5513a02d622ba89e76baf4b49d25ae20c2c2c623fced12b0d6dd7b8f23e006" dependencies = [ "ark-bn254", "ark-ec", @@ -3844,11 +4124,12 @@ dependencies = [ "ark-serialize", "base64 0.21.7", "bincode", - "bitflags 2.4.1", + "bitflags 2.5.0", "blake3", "borsh 0.10.3", "borsh 0.9.3", - "bs58 0.4.0", + "borsh 1.5.0", + "bs58", "bv", "bytemuck", "cc", @@ -3865,7 +4146,7 @@ dependencies = [ "log", "memoffset 0.9.0", "num-bigint 0.4.4", - "num-derive 0.3.3", + "num-derive 0.4.1", "num-traits", "parking_lot", "rand 0.8.5", @@ -3888,9 +4169,9 @@ dependencies = [ [[package]] name = "solana-program-runtime" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5dbb56d36cc15b4cf5a71c0ce6262a263212f7a312b0dbc41b226654329c37" +checksum = "64dc9f666a8e4f93166ce58eea9dfbf275e5cad461b2f1bbfa06538718dc3212" dependencies = [ "base64 0.21.7", "bincode", @@ -3899,7 +4180,7 @@ dependencies = [ "itertools", "libc", "log", - "num-derive 0.3.3", + "num-derive 0.4.1", "num-traits", "percentage", "rand 0.8.5", @@ -3916,9 +4197,9 @@ dependencies = [ [[package]] name = "solana-program-test" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61bbf119c35d4393702953e586b72053c3b80a92c781931cd412d53d2036475e" +checksum = "2760112327ffce892f6a21030f7c9e4b6da3ded8c8eadf1dbfffcb5a754c61db" dependencies = [ "assert_matches", "async-trait", @@ -3946,9 +4227,9 @@ dependencies = [ [[package]] name = "solana-pubsub-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c22290c0d296a6a250a8d5b680797f12138a81af9c403a6ce62bd3ddad307e6" +checksum = "5ffdcbdad685b87475a91909fdb442d2edfabc2870110580c7f0cf7eb7883f97" dependencies = [ "crossbeam-channel", "futures-util", @@ -3971,9 +4252,9 @@ dependencies = [ [[package]] name = "solana-quic-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f924d8722f9e910d790678a79c2a0bfed786dffe1aefa5d769f8548679794263" +checksum = "056e909037b05097d2ff0181cb7e3d26876d8dff6d50701463a61e990cf84afd" dependencies = [ "async-mutex", "async-trait", @@ -3998,9 +4279,9 @@ dependencies = [ [[package]] name = "solana-rayon-threadlimit" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0a2e484e5b272690ac1431a6821f2b5180149d67c56934d9e007224ced15d0" +checksum = "e93a5e1ef891dca2cca907f7196b6a5d3b80af4183f2be0f981906b16711ff5d" dependencies = [ "lazy_static", "num_cpus", @@ -4008,14 +4289,14 @@ dependencies = [ [[package]] name = "solana-remote-wallet" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb9a96d1c001d07a0abb08e05b92ff6528b2d9239d03c57f99f738527839eb12" +checksum = "52c06eaf47d9a98ba22e890e68868f5d48c91e01268c541a53b5960288b617d6" dependencies = [ "console", "dialoguer", "log", - "num-derive 0.3.3", + "num-derive 0.4.1", "num-traits", "parking_lot", "qstring", @@ -4027,14 +4308,14 @@ dependencies = [ [[package]] name = "solana-rpc-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91503edfdb2ba9c5e0127048e7795f22e050cf2bcee1259361af113d533b4b26" +checksum = "ed1d4b6f1f4e3dab7509401e85edc1c1ac208c61819de90178e01cf162c9c051" dependencies = [ "async-trait", "base64 0.21.7", "bincode", - "bs58 0.4.0", + "bs58", "indicatif", "log", "reqwest", @@ -4053,12 +4334,12 @@ dependencies = [ [[package]] name = "solana-rpc-client-api" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131662e5eea4fa5fc88b01f07d9e430315c0976be848ba3994244249c5fb033a" +checksum = "a31feddef24d3e0aab189571adea7f109639ef6179fcd3cd34ffc8c73d3409f1" dependencies = [ "base64 0.21.7", - "bs58 0.4.0", + "bs58", "jsonrpc-core", "reqwest", "semver", @@ -4075,9 +4356,9 @@ dependencies = [ [[package]] name = "solana-rpc-client-nonce-utils" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67cdff955b9994ae240f6f287420c6727a581120c02ccc4f2fa535886732a1d" +checksum = "1837728262063723c659e4b8c0acf0baa99cd38cb333511456465d2c9e654474" dependencies = [ "clap 2.34.0", "solana-clap-utils", @@ -4088,10 +4369,11 @@ dependencies = [ [[package]] name = "solana-runtime" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf63159e669f29065c9ff280c09f5b96139b00258502ee401338150fce78fed7" +checksum = "9a3480088ad0ffb701ada496f19754b4ff737e516c6b5f1231508e50ae2e0ea3" dependencies = [ + "aquamarine", "arrayref", "base64 0.21.7", "bincode", @@ -4105,7 +4387,6 @@ dependencies = [ "dir-diff", "flate2", "fnv", - "fs-err", "im", "index_list", "itertools", @@ -4114,11 +4395,12 @@ dependencies = [ "lru", "lz4", "memmap2", + "mockall", "modular-bitfield", - "num-derive 0.3.3", + "num-derive 0.4.1", "num-traits", "num_cpus", - "num_enum 0.6.1", + "num_enum 0.7.2", "ouroboros", "percentage", "qualifier_attr", @@ -4129,7 +4411,6 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "siphasher", "solana-accounts-db", "solana-address-lookup-table-program", "solana-bpf-loader-program", @@ -4154,8 +4435,8 @@ dependencies = [ "solana-zk-token-proof-program", "solana-zk-token-sdk", "static_assertions", - "strum", - "strum_macros", + "strum 0.24.1", + "strum_macros 0.24.3", "symlink", "tar", "tempfile", @@ -4165,16 +4446,16 @@ dependencies = [ [[package]] name = "solana-sdk" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb34583922c5e79004ad8d8d69f333d274d21b614f0e1a575f325fc29a104ec2" +checksum = "8f50cac89269a01235f6b421bc580132191f4df388f4265513e78fd00cf864dd" dependencies = [ "assert_matches", "base64 0.21.7", "bincode", - "bitflags 2.4.1", - "borsh 0.10.3", - "bs58 0.4.0", + "bitflags 2.5.0", + "borsh 1.5.0", + "bs58", "bytemuck", "byteorder", "chrono", @@ -4190,9 +4471,9 @@ dependencies = [ "libsecp256k1", "log", "memmap2", - "num-derive 0.3.3", + "num-derive 0.4.1", "num-traits", - "num_enum 0.6.1", + "num_enum 0.7.2", "pbkdf2 0.11.0", "qstring", "qualifier_attr", @@ -4207,6 +4488,7 @@ dependencies = [ "serde_with", "sha2 0.10.8", "sha3 0.10.8", + "siphasher", "solana-frozen-abi", "solana-frozen-abi-macro", "solana-logger", @@ -4219,11 +4501,11 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f58786e949f43b8c9b826fdfa5ad8586634b077ab04f989fb8e30535786712" +checksum = "5cb099b2f9c0a65a6f23ced791325141cd68c27b04d11c04fef838a00f613861" dependencies = [ - "bs58 0.4.0", + "bs58", "proc-macro2", "quote", "rustversion", @@ -4238,9 +4520,9 @@ checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" [[package]] name = "solana-send-transaction-service" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9987eccfe96b38785d95840277da4238de4f01166d0c32ec9bbfc5a319f4a530" +checksum = "0deed4fe8bb31ff178d8b7e8295bc81e6e1d704fc0e2c5565f58d9eb8feec89d" dependencies = [ "crossbeam-channel", "log", @@ -4254,9 +4536,9 @@ dependencies = [ [[package]] name = "solana-stake-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab247c866dab350bf610df8e1fab97ae0a0519cb81914348d382eac9e80940d" +checksum = "4ea02d44b82ed0eb271871cf8a1b8179a0ab50f4f995e7d8ae691c1971bd0a0e" dependencies = [ "bincode", "log", @@ -4269,9 +4551,9 @@ dependencies = [ [[package]] name = "solana-streamer" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efe4c33e0f68ea7a3701650badf6753b85fef2100cac6bc187c8e443e61c53da" +checksum = "f8a20843e8370adb3c04f47caa79ffdc92ae1bf078ad26530be1bca5d7bdd5d2" dependencies = [ "async-channel", "bytes", @@ -4291,6 +4573,7 @@ dependencies = [ "rand 0.8.5", "rcgen", "rustls", + "smallvec", "solana-metrics", "solana-perf", "solana-sdk", @@ -4301,9 +4584,9 @@ dependencies = [ [[package]] name = "solana-system-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24147d17f13bef6548d15a7fc63eb8a3271523f7ffc91f13032944b0dc34f974" +checksum = "01294e45b407b7d4c8ff546af6f60344efd6591cf162c88e231ee3ba2c544672" dependencies = [ "bincode", "log", @@ -4315,9 +4598,9 @@ dependencies = [ [[package]] name = "solana-thin-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e782aabf9443a36d65e74d70ce732cc844707a5fec5a498bcbd81d3de7598c" +checksum = "c74da8f36b89b28c47e5ba3bad5279ff3dfea5829154882845d4821fc76ff497" dependencies = [ "bincode", "log", @@ -4330,9 +4613,9 @@ dependencies = [ [[package]] name = "solana-tpu-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980bee30cbfe3c51f973da7fdcccb9df2c2d9b9175c06066b293499e02108fd4" +checksum = "d0f2fd4b4aeffa14b9c5be9913072ea8e72ca261254a65a999f3d2fd70e7a660" dependencies = [ "async-trait", "bincode", @@ -4354,15 +4637,15 @@ dependencies = [ [[package]] name = "solana-transaction-status" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c180013e406418d593ce7b51da7007a638ace18261de14901b090e53a1d7025" +checksum = "3efa0d30f78dbc74e795638b053dd6ec7230739301e7f0e06b586f7731fd25c8" dependencies = [ "Inflector", "base64 0.21.7", "bincode", "borsh 0.10.3", - "bs58 0.4.0", + "bs58", "lazy_static", "log", "serde", @@ -4379,9 +4662,9 @@ dependencies = [ [[package]] name = "solana-udp-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab995970a424c89b7966a01aec90cdf1685c49aacf38a5f463200fc273a7d86b" +checksum = "32af58cadd37be19d04e0f3877104b8640bccc4be8ca1dbf431549b399b784c2" dependencies = [ "async-trait", "solana-connection-cache", @@ -4394,9 +4677,9 @@ dependencies = [ [[package]] name = "solana-version" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b32cc394aa7132ab7f270801b98bf47fa585ab93f1038e5be27e480d7b5b2dca" +checksum = "42c7cef8aa9f1c633bf09dd91b8e635b6b30c40236652031b1800b245dc1bd02" dependencies = [ "log", "rustc_version", @@ -4410,9 +4693,9 @@ dependencies = [ [[package]] name = "solana-vote" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6092058284f0e02274177c45a22032eb7288aa4f6f8003ed469b1a562cac3bd" +checksum = "12945ee508c751ffdce58f976be6e58a05529ce0032c1f7db76eed6a8d76b33c" dependencies = [ "crossbeam-channel", "itertools", @@ -4429,13 +4712,13 @@ dependencies = [ [[package]] name = "solana-vote-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589cad4dccb4392e23f5ae4ccdd1f0aaa10f2823b264b27c4feb6382f40f4fd4" +checksum = "725a39044d455c08fe83fca758e94e5ddfaa25f6e2e2cfd5c31d7afdcad8de38" dependencies = [ "bincode", "log", - "num-derive 0.3.3", + "num-derive 0.4.1", "num-traits", "rustc_version", "serde", @@ -4451,12 +4734,12 @@ dependencies = [ [[package]] name = "solana-zk-token-proof-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6b02ddeb2ab414b513b523aa678fac81109214f08d5c080165c15483a22cce" +checksum = "39263f3e47a160b9b67896f2225d56e6872905c066152cbe61f5fd201c52a6d2" dependencies = [ "bytemuck", - "num-derive 0.3.3", + "num-derive 0.4.1", "num-traits", "solana-program-runtime", "solana-sdk", @@ -4465,9 +4748,9 @@ dependencies = [ [[package]] name = "solana-zk-token-sdk" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d932d7b13a223a6c1068d7061df7e9d2de14bfc0a874350eef19d59086b04a" +checksum = "630dc0b5f6250cf6a4c8b2bd3895283738915e83eba5453db20bb02b2527f302" dependencies = [ "aes-gcm-siv", "base64 0.21.7", @@ -4479,7 +4762,7 @@ dependencies = [ "itertools", "lazy_static", "merlin", - "num-derive 0.3.3", + "num-derive 0.4.1", "num-traits", "rand 0.7.3", "serde", @@ -4764,7 +5047,16 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ - "strum_macros", + "strum_macros 0.24.3", +] + +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +dependencies = [ + "strum_macros 0.26.2", ] [[package]] @@ -4780,6 +5072,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "strum_macros" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.48", +] + [[package]] name = "subtle" version = "2.4.1" @@ -4814,6 +5119,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "synstructure" version = "0.12.6" @@ -4915,6 +5232,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "test-case" version = "3.3.1" @@ -5178,9 +5501,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" @@ -5195,9 +5518,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap 2.1.0", "toml_datetime", @@ -5391,6 +5714,14 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utils" +version = "2.0.0" +dependencies = [ + "bytemuck", + "solana-program", +] + [[package]] name = "valuable" version = "0.1.0" @@ -5409,6 +5740,17 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "visibility" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3fd98999db9227cf28e59d83e1f120f42bc233d4b152e8fab9bc87d5bb1e0f8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "void" version = "1.0.2" @@ -5448,9 +5790,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -5458,9 +5800,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -5485,9 +5827,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5495,9 +5837,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -5508,9 +5850,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" diff --git a/Cargo.toml b/Cargo.toml index c74fce2..9e3dcb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,38 +1,28 @@ -[package] -name = "ore-program" -version = "1.2.1" -description = "Ore is a digital currency you can mine from anywhere, at home or on your phone." +[workspace] +resolver = "2" +members = ["core/*", "utils"] + +[workspace.package] +version = "2.0.0" edition = "2021" license = "Apache-2.0" homepage = "https://ore.supply" documentation = "https://ore.supply" -repository = "https://github.com/hardhatchad/ore" +repository = "https://github.com/regolith-labs/ore" readme = "./README.md" keywords = ["solana", "crypto", "mining"] -[lib] -crate-type = ["cdylib", "lib"] -name = "ore" - -[features] -no-entrypoint = [] -default = [] - -[dependencies] -bs58 = "0.5.0" +[workspace.dependencies] +array-const-fn-init = "0.1.1" bytemuck = "1.14.3" +const-crypto = "0.1.0" +drillx = { git = "https://github.com/regolith-labs/drillx", branch = "master", features = ["solana"] } mpl-token-metadata = "4.1.2" num_enum = "0.7.2" shank = "0.3.0" -solana-program = "^1.16" +solana-program = "1.18" spl-token = { version = "^4", features = ["no-entrypoint"] } spl-associated-token-account = { version = "^2.2", features = [ "no-entrypoint" ] } static_assertions = "1.1.0" thiserror = "1.0.57" - -[dev-dependencies] -bs64 = "0.1.2" -rand = "0.8.5" -solana-program-test = "^1.16" -solana-sdk = "^1.16" -tokio = { version = "1.35", features = ["full"] } +utils = { path = "utils" } diff --git a/README.md b/README.md index 337a941..2264f89 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,8 @@ -# Ore +# ORE -**Ore is a digital currency you can mine from anywhere, at home or on your phone.** It uses a novel proof-of-work algorithm to guarantee no miner can ever be starved out from earning rewards. +**ORE is a fair-launch, proof-of-work, digital currency everyone can mine.** -## How it works - -The primary innovation of Ore is to offer non-exclusive mining rewards. This means one miner finding a valid solution does not prevent another miner from finding one as well. Rather than setting up every miner in a winner-take-all competition against one another, Ore gives each miner a personalized computational challenge. As long as a miner provides a valid solution to their own individual challenge, the protocol guarantees they will earn a piece of the supply. Since no miner can be censored from the network and valid solutions are non-exclusive, starvation is avoided. - - -## Supply - -Ore is designed to protect holders from runaway supply inflation. Regardless of how many miners are active in the world, supply growth is strictly bounded to a rate of `0 ≤ R ≤ 2 ORE/min`. In other words, linear. The mining reward rate – amount paid out to miners per valid solution – is dynamically adjusted every 60 seconds to maintain an average supply growth of `1 ORE/min`. This level was chosen for its straightforward simplicity, scale agnosticism, and for striking a balance between the extremes of exponential inflation on one hand and stagnant deflation on the other. - - -## Program -- [`Consts`](src/consts.rs) – Program constants. -- [`Entrypoint`](src/lib.rs) – The program entrypoint. -- [`Errors`](src/error.rs) – Custom program errors. -- [`Idl`](idl/ore.json) – Interface for clients, explorers, and programs. -- [`Instruction`](src/instruction.rs) – Declared instructions and arguments. -- [`Loaders`](src/loaders.rs) – Validation logic for loading Solana accounts. - - -## Instructions -- [`Initialize`](src/processor/initialize.rs) – Initializes the Ore program, creating the bus, mint, and treasury accounts. -- [`Reset`](src/processor/reset.rs) – Resets the program for a new epoch. -- [`Register`](src/processor/register.rs) – Creates a new proof account for a prospective miner. -- [`Mine`](src/processor/mine.rs) – Verifies a hash provided by a miner and issues claimable rewards. -- [`Claim`](src/processor/claim.rs) – Distributes claimable rewards as tokens from the treasury to a miner. -- [`UpdateAdmin`](src/processor/update_admin.rs) – Updates the admin authority. -- [`UpdateDifficulty`](src/processor/update_difficulty.rs) - Updates the hashing difficulty. - - -## State - - [`Bus`](src/state/bus.rs) - An account (8 total) which tracks and limits the amount mined rewards each epoch. - - [`Proof`](src/state/proof.rs) - An account (1 per miner) which tracks a miner's hash, claimable rewards, and lifetime stats. - - [`Treasury`](src/state/treasury.rs) – A singleton account which manages program-wide variables and authorities. - - -## Tests - -To run the test suite, use the Solana toolchain: - -``` -cargo test-sbf -``` - -For line coverage, use llvm-cov: - -``` -cargo llvm-cov -``` +## Programs +- [`Core`](core) - ORE mining program. +- [`Stake`](stake) - ORE staking program. diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000..816aaeb --- /dev/null +++ b/core/README.md @@ -0,0 +1,44 @@ +# ORE + +**ORE is a fair-launch, proof-of-work, digital currency everyone can mine.** + + +## API +- [`Consts`](api/src/consts.rs) – Program constants. +- [`Entrypoint`](api/src/lib.rs) – The program entrypoint. +- [`Error`](api/src/error.rs) – Custom program errors. +- [`Event`](api/src/error.rs) – Custom program events. +- [`Instruction`](api/src/instruction.rs) – Declared instructions and arguments. + +## Instructions +- [`Claim`](program/src/claim.rs) – Distributes claimable rewards as tokens from the treasury to a miner. +- [`Close`](program/src/close.rs) – Closes a proof account returns the rent to the owner. +- [`Crown`](program/src/crown.rs) – Flags a proof account as the top staker on the network. +- [`Open`](program/src/open.rs) – Creates a new proof account for a prospective miner. +- [`Mine`](program/src/mine.rs) – Verifies a hash provided by a miner and issues claimable rewards. +- [`Stake`](program/src/stake.rs) – Stakes ORE with a miner to increase their multiplier. +- [`Reset`](program/src/reset.rs) – Resets the program for a new epoch. +- [`Update`](program/src/update.rs) – Updates a proof account's miner authority. +- [`Upgrade`](program/src/upgrade.rs) – Migrates ORE v1 tokens to ORE v2, one-for-one. +- [`Initialize`](program/src/initialize.rs) – Initializes the Ore program, creating the bus, mint, and treasury accounts. + +## State + - [`Bus`](api/src/state/bus.rs) - An account (8 total) which tracks and limits the amount ORE mined each epoch. + - [`Config`](api/src/state/config.rs) – A singleton account which manages program-wide variables. + - [`Proof`](api/src/state/proof.rs) - An account (1 per user) which tracks a miner's current hash and current stake. + - [`Treasury`](api/src/state/treasury.rs) – A singleton account which has authority to mint ORE and holds onto user stake. + + +## Tests + +To run the test suite, use the Solana toolchain: + +``` +cargo test-sbf +``` + +For line coverage, use llvm-cov: + +``` +cargo llvm-cov +``` diff --git a/core/api/Cargo.toml b/core/api/Cargo.toml new file mode 100644 index 0000000..224c77f --- /dev/null +++ b/core/api/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "ore-api" +description = "API for interacting with the ORE program" +version.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true +keywords.workspace = true + +[dependencies] +array-const-fn-init.workspace = true +bytemuck.workspace = true +const-crypto.workspace = true +drillx.workspace = true +mpl-token-metadata.workspace = true +num_enum.workspace = true +shank.workspace = true +solana-program.workspace = true +spl-token.workspace = true +spl-associated-token-account.workspace = true +static_assertions.workspace = true +thiserror.workspace = true +utils.workspace = true \ No newline at end of file diff --git a/core/api/src/consts.rs b/core/api/src/consts.rs new file mode 100644 index 0000000..92f437a --- /dev/null +++ b/core/api/src/consts.rs @@ -0,0 +1,133 @@ +use array_const_fn_init::array_const_fn_init; +use const_crypto::ed25519; +use solana_program::{pubkey, pubkey::Pubkey}; + +/// The reward rate to intialize the program with. +pub const INITIAL_BASE_REWARD_RATE: u64 = 10u64.pow(3u32); + +/// The admin allowed to initialize the program. +pub const INITIAL_ADMIN: Pubkey = pubkey!("HBUh9g46wk2X89CvaNN15UmsznP59rh6od1h8JwYAopk"); + +/// The spam/liveness tolerance in seconds. +pub const TOLERANCE: i64 = 5; + +/// The minimum difficulty required of all submitted hashes. +pub const MIN_DIFFICULTY: u32 = 8; + +/// The decimal precision of the ORE token. +/// There are 100 billion indivisible units per ORE (called "grains"). +pub const TOKEN_DECIMALS: u8 = 11; + +/// The decimal precision of the ORE v1 token. +pub const TOKEN_DECIMALS_V1: u8 = 9; + +/// One ORE token, denominated in indivisible units. +pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32); + +/// The duration of one minute, in seconds. +pub const ONE_MINUTE: i64 = 60; + +/// The number of minutes in a program epoch. +pub const EPOCH_MINUTES: i64 = 1; + +/// The duration of a program epoch, in seconds. +pub const EPOCH_DURATION: i64 = ONE_MINUTE.saturating_mul(EPOCH_MINUTES); + +/// The maximum token supply (21 million). +pub const MAX_SUPPLY: u64 = ONE_ORE.saturating_mul(21_000_000); + +/// The target quantity of ORE to be mined per epoch. +pub const TARGET_EPOCH_REWARDS: u64 = ONE_ORE.saturating_mul(EPOCH_MINUTES as u64); + +/// The maximum quantity of ORE that can be mined per epoch. +/// Inflation rate ≈ 1 ORE / min (min 0, max 8) +pub const MAX_EPOCH_REWARDS: u64 = TARGET_EPOCH_REWARDS.saturating_mul(BUS_COUNT as u64); + +/// The quantity of ORE each bus is allowed to issue per epoch. +pub const BUS_EPOCH_REWARDS: u64 = MAX_EPOCH_REWARDS.saturating_div(BUS_COUNT as u64); + +/// The number of bus accounts, for parallelizing mine operations. +pub const BUS_COUNT: usize = 8; + +/// The smoothing factor for reward rate changes. The reward rate cannot change by more or less +/// than a factor of this constant from one epoch to the next. +pub const SMOOTHING_FACTOR: u64 = 2; + +// Assert MAX_EPOCH_REWARDS is evenly divisible by BUS_COUNT. +static_assertions::const_assert!( + (MAX_EPOCH_REWARDS / BUS_COUNT as u64) * BUS_COUNT as u64 == MAX_EPOCH_REWARDS +); + +/// The seed of the bus account PDA. +pub const BUS: &[u8] = b"bus"; + +/// The seed of the config account PDA. +pub const CONFIG: &[u8] = b"config"; + +/// The seed of the metadata account PDA. +pub const METADATA: &[u8] = b"metadata"; + +/// The seed of the mint account PDA. +pub const MINT: &[u8] = b"mint"; + +/// The seed of proof account PDAs. +pub const PROOF: &[u8] = b"proof"; + +/// The seed of the treasury account PDA. +pub const TREASURY: &[u8] = b"treasury"; + +/// Noise for deriving the mint pda +pub const MINT_NOISE: [u8; 16] = [ + 166, 199, 85, 221, 225, 119, 21, 185, 160, 82, 242, 237, 194, 84, 250, 252, +]; + +/// The name for token metadata. +pub const METADATA_NAME: &str = "ORE"; + +/// The ticker symbol for token metadata. +pub const METADATA_SYMBOL: &str = "ORE"; + +/// The uri for token metdata. +pub const METADATA_URI: &str = "https://ore.supply/metadata.json"; + +/// Program id for const pda derivations +const PROGRAM_ID: [u8; 32] = unsafe { *(&crate::id() as *const Pubkey as *const [u8; 32]) }; + +/// The addresses of the bus accounts. +pub const BUS_ADDRESSES: [Pubkey; BUS_COUNT] = array_const_fn_init![const_bus_address; 8]; + +/// Function to derive const bus addresses. +const fn const_bus_address(i: usize) -> Pubkey { + Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[i as u8]], &PROGRAM_ID).0) +} + +/// The address of the config account. +pub const CONFIG_ADDRESS: Pubkey = + Pubkey::new_from_array(ed25519::derive_program_address(&[CONFIG], &PROGRAM_ID).0); + +/// The address of the mint metadata account. +pub const METADATA_ADDRESS: Pubkey = Pubkey::new_from_array( + ed25519::derive_program_address( + &[ + METADATA, + unsafe { &*(&mpl_token_metadata::ID as *const Pubkey as *const [u8; 32]) }, + unsafe { &*(&MINT_ADDRESS as *const Pubkey as *const [u8; 32]) }, + ], + unsafe { &*(&mpl_token_metadata::ID as *const Pubkey as *const [u8; 32]) }, + ) + .0, +); + +/// The address of the mint account. +pub const MINT_ADDRESS: Pubkey = + Pubkey::new_from_array(ed25519::derive_program_address(&[MINT, &MINT_NOISE], &PROGRAM_ID).0); + +/// The address of the v1 mint account. +pub const MINT_V1_ADDRESS: Pubkey = pubkey!("oreoN2tQbHXVaZsr3pf66A48miqcBXCDJozganhEJgz"); + +/// The address of the treasury account. +pub const TREASURY_ADDRESS: Pubkey = + Pubkey::new_from_array(ed25519::derive_program_address(&[TREASURY], &PROGRAM_ID).0); + +/// The bump of the treasury account, for cpis. +pub const TREASURY_BUMP: u8 = ed25519::derive_program_address(&[TREASURY], &PROGRAM_ID).1; diff --git a/core/api/src/error.rs b/core/api/src/error.rs new file mode 100644 index 0000000..a0fdbf9 --- /dev/null +++ b/core/api/src/error.rs @@ -0,0 +1,32 @@ +use num_enum::IntoPrimitive; +use solana_program::program_error::ProgramError; +use thiserror::Error; + +#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, IntoPrimitive)] +#[repr(u32)] +pub enum OreError { + #[error("The epoch has ended and needs reset")] + NeedsReset = 0, + #[error("The provided hash is invalid")] + HashInvalid = 1, + #[error("The provided hash did not satisfy the minimum required difficulty")] + HashTooEasy = 2, + #[error("The claim amount cannot be greater than the claimable rewards")] + ClaimTooLarge = 3, + #[error("The clock time is invalid")] + ClockInvalid = 4, + #[error("You are trying to submit too soon")] + Spam = 5, + #[error("Only one hash may be validated per transaction")] + TransactionInvalid = 6, + #[error("The tolerance cannot exceed i64 max value")] + ToleranceOverflow = 7, + #[error("The maximum supply has been reached")] + MaxSupply = 8, +} + +impl From for ProgramError { + fn from(e: OreError) -> Self { + ProgramError::Custom(e as u32) + } +} diff --git a/core/api/src/event.rs b/core/api/src/event.rs new file mode 100644 index 0000000..675e9e9 --- /dev/null +++ b/core/api/src/event.rs @@ -0,0 +1,13 @@ +use bytemuck::{Pod, Zeroable}; + +use crate::utils::impl_to_bytes; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct MineEvent { + pub difficulty: u64, + pub reward: u64, + pub timing: i64, +} + +impl_to_bytes!(MineEvent); diff --git a/src/instruction.rs b/core/api/src/instruction.rs similarity index 60% rename from src/instruction.rs rename to core/api/src/instruction.rs index ca052d7..5d00d43 100644 --- a/src/instruction.rs +++ b/core/api/src/instruction.rs @@ -1,4 +1,5 @@ use bytemuck::{Pod, Zeroable}; +use drillx::Solution; use num_enum::TryFromPrimitive; use shank::ShankInstruction; use solana_program::{ @@ -8,14 +9,51 @@ use solana_program::{ }; use crate::{ - impl_instruction_from_bytes, impl_to_bytes, state::Hash, BUS, METADATA, MINT, MINT_ADDRESS, - MINT_NOISE, PROOF, TREASURY, TREASURY_ADDRESS, + consts::*, + utils::{impl_instruction_from_bytes, impl_to_bytes}, }; #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, ShankInstruction, TryFromPrimitive)] #[rustfmt::skip] pub enum OreInstruction { + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "beneficiary", desc = "Beneficiary token account", writable)] + #[account(3, name = "proof", desc = "Ore proof account", writable)] + #[account(4, name = "treasury", desc = "Ore treasury account", writable)] + #[account(5, name = "treasury_tokens", desc = "Ore treasury token account", writable)] + #[account(6, name = "token_program", desc = "SPL token program")] + Claim = 0, + + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "proof", desc = "Ore proof account", writable)] + #[account(3, name = "system_program", desc = "Solana system program")] + Close = 1, + + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "config", desc = "Ore config account", writable)] + #[account(3, name = "proof", desc = "Ore proof account – current top staker")] + #[account(4, name = "proof_new", desc = "Ore proof account – new top staker")] + Crown = 2, + + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "bus", desc = "Ore bus account", writable)] + #[account(3, name = "config", desc = "Ore config account")] + #[account(4, name = "noise", desc = "Ore noise account")] + #[account(5, name = "proof", desc = "Ore proof account", writable)] + #[account(6, name = "slot_hashes", desc = "Solana slot hashes sysvar")] + Mine = 3, + + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "proof", desc = "Ore proof account", writable)] + #[account(3, name = "system_program", desc = "Solana system program")] + Open = 4, + #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "bus_0", desc = "Ore bus account 0", writable)] @@ -26,35 +64,36 @@ pub enum OreInstruction { #[account(7, name = "bus_5", desc = "Ore bus account 5", writable)] #[account(8, name = "bus_6", desc = "Ore bus account 6", writable)] #[account(9, name = "bus_7", desc = "Ore bus account 7", writable)] - #[account(10, name = "mint", desc = "Ore token mint account", writable)] - #[account(11, name = "treasury", desc = "Ore treasury account", writable)] - #[account(12, name = "treasury_tokens", desc = "Ore treasury token account", writable)] - #[account(13, name = "token_program", desc = "SPL token program")] - Reset = 0, + #[account(10, name = "config", desc = "Ore config account")] + #[account(11, name = "mint", desc = "Ore token mint account", writable)] + #[account(12, name = "treasury", desc = "Ore treasury account", writable)] + #[account(13, name = "treasury_tokens", desc = "Ore treasury token account", writable)] + #[account(14, name = "token_program", desc = "SPL token program")] + Reset = 5, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "proof", desc = "Ore proof account", writable)] - #[account(3, name = "system_program", desc = "Solana system program")] - Register = 1, - + #[account(3, name = "sender", desc = "Signer token account", writable)] + #[account(4, name = "treasury_tokens", desc = "Ore treasury token account", writable)] + #[account(5, name = "token_program", desc = "SPL token program")] + Stake = 6, + #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] - #[account(2, name = "bus", desc = "Ore bus account", writable)] - #[account(3, name = "proof", desc = "Ore proof account", writable)] - #[account(4, name = "treasury", desc = "Ore treasury account")] - #[account(5, name = "slot_hashes", desc = "Solana slot hashes sysvar")] - Mine = 2, + #[account(2, name = "proof", desc = "Ore proof account", writable)] + Update = 7, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "beneficiary", desc = "Beneficiary token account", writable)] - #[account(3, name = "proof", desc = "Ore proof account", writable)] + #[account(3, name = "sender", desc = "Signer token account", writable)] #[account(4, name = "treasury", desc = "Ore treasury account", writable)] - #[account(5, name = "treasury_tokens", desc = "Ore treasury token account", writable)] - #[account(6, name = "token_program", desc = "SPL token program")] - Claim = 3, - + #[account(5, name = "mint", desc = "Ore token mint account", writable)] + #[account(6, name = "mint_v1", desc = "Ore v1 token mint account", writable)] + #[account(7, name = "token_program", desc = "SPL token program")] + Upgrade = 8, + #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] #[account(2, name = "bus_0", desc = "Ore bus account 0", writable)] @@ -67,24 +106,15 @@ pub enum OreInstruction { #[account(9, name = "bus_7", desc = "Ore bus account 7", writable)] #[account(10, name = "metadata", desc = "Ore mint metadata account", writable)] #[account(11, name = "mint", desc = "Ore mint account", writable)] - #[account(12, name = "treasury", desc = "Ore treasury account", writable)] - #[account(13, name = "treasury_tokens", desc = "Ore treasury token account", writable)] - #[account(14, name = "system_program", desc = "Solana system program")] - #[account(15, name = "token_program", desc = "SPL token program")] - #[account(16, name = "associated_token_program", desc = "SPL associated token program")] - #[account(17, name = "mpl_metadata_program", desc = "Metaplex metadata program")] - #[account(18, name = "rent", desc = "Solana rent sysvar")] + #[account(12, name = "noise", desc = "Ore noise account", writable)] + #[account(13, name = "treasury", desc = "Ore treasury account", writable)] + #[account(14, name = "treasury_tokens", desc = "Ore treasury token account", writable)] + #[account(15, name = "system_program", desc = "Solana system program")] + #[account(16, name = "token_program", desc = "SPL token program")] + #[account(17, name = "associated_token_program", desc = "SPL associated token program")] + #[account(18, name = "mpl_metadata_program", desc = "Metaplex metadata program")] + #[account(19, name = "rent", desc = "Solana rent sysvar")] Initialize = 100, - - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "signer", desc = "Admin signer", signer)] - #[account(2, name = "treasury", desc = "Ore treasury account")] - UpdateAdmin = 101, - - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "signer", desc = "Admin signer", signer)] - #[account(2, name = "treasury", desc = "Ore treasury account")] - UpdateDifficulty = 102, } impl OreInstruction { @@ -104,6 +134,7 @@ pub struct InitializeArgs { pub bus_5_bump: u8, pub bus_6_bump: u8, pub bus_7_bump: u8, + pub config_bump: u8, pub metadata_bump: u8, pub mint_bump: u8, pub treasury_bump: u8, @@ -111,14 +142,14 @@ pub struct InitializeArgs { #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct RegisterArgs { +pub struct OpenArgs { pub bump: u8, } #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct MineArgs { - pub hash: Hash, + pub digest: [u8; 16], pub nonce: [u8; 8], } @@ -130,107 +161,29 @@ pub struct ClaimArgs { #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct UpdateAdminArgs { - pub new_admin: Pubkey, +pub struct StakeArgs { + pub amount: [u8; 8], } #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct UpdateDifficultyArgs { - pub new_difficulty: Hash, +pub struct UpgradeArgs { + pub amount: [u8; 8], } impl_to_bytes!(InitializeArgs); -impl_to_bytes!(RegisterArgs); +impl_to_bytes!(OpenArgs); impl_to_bytes!(MineArgs); impl_to_bytes!(ClaimArgs); -impl_to_bytes!(UpdateAdminArgs); -impl_to_bytes!(UpdateDifficultyArgs); +impl_to_bytes!(StakeArgs); +impl_to_bytes!(UpgradeArgs); impl_instruction_from_bytes!(InitializeArgs); -impl_instruction_from_bytes!(RegisterArgs); +impl_instruction_from_bytes!(OpenArgs); impl_instruction_from_bytes!(MineArgs); impl_instruction_from_bytes!(ClaimArgs); -impl_instruction_from_bytes!(UpdateAdminArgs); -impl_instruction_from_bytes!(UpdateDifficultyArgs); - -/// Builds a reset instruction. -pub fn reset(signer: Pubkey) -> Instruction { - let bus_0 = Pubkey::find_program_address(&[BUS, &[0]], &crate::id()).0; - let bus_1 = Pubkey::find_program_address(&[BUS, &[1]], &crate::id()).0; - let bus_2 = Pubkey::find_program_address(&[BUS, &[2]], &crate::id()).0; - let bus_3 = Pubkey::find_program_address(&[BUS, &[3]], &crate::id()).0; - let bus_4 = Pubkey::find_program_address(&[BUS, &[4]], &crate::id()).0; - let bus_5 = Pubkey::find_program_address(&[BUS, &[5]], &crate::id()).0; - let bus_6 = Pubkey::find_program_address(&[BUS, &[6]], &crate::id()).0; - let bus_7 = Pubkey::find_program_address(&[BUS, &[7]], &crate::id()).0; - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus_0, false), - AccountMeta::new(bus_1, false), - AccountMeta::new(bus_2, false), - AccountMeta::new(bus_3, false), - AccountMeta::new(bus_4, false), - AccountMeta::new(bus_5, false), - AccountMeta::new(bus_6, false), - AccountMeta::new(bus_7, false), - AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: OreInstruction::Reset.to_vec(), - } -} - -/// Builds a register instruction. -pub fn register(signer: Pubkey) -> Instruction { - let proof_pda = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(proof_pda.0, false), - AccountMeta::new_readonly(solana_program::system_program::id(), false), - ], - data: [ - OreInstruction::Register.to_vec(), - RegisterArgs { bump: proof_pda.1 }.to_bytes().to_vec(), - ] - .concat(), - } -} - -/// Builds a mine instruction. -pub fn mine(signer: Pubkey, bus: Pubkey, hash: Hash, nonce: u64) -> Instruction { - let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus, false), - AccountMeta::new(proof, false), - AccountMeta::new_readonly(TREASURY_ADDRESS, false), - AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), - ], - data: [ - OreInstruction::Mine.to_vec(), - MineArgs { - hash, - nonce: nonce.to_le_bytes(), - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} +impl_instruction_from_bytes!(StakeArgs); +impl_instruction_from_bytes!(UpgradeArgs); /// Builds a claim instruction. pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { @@ -244,8 +197,9 @@ pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { accounts: vec![ AccountMeta::new(signer, true), AccountMeta::new(beneficiary, false), + AccountMeta::new(MINT_ADDRESS, false), AccountMeta::new(proof, false), - AccountMeta::new(TREASURY_ADDRESS, false), + AccountMeta::new_readonly(TREASURY_ADDRESS, false), AccountMeta::new(treasury_tokens, false), AccountMeta::new_readonly(spl_token::id(), false), ], @@ -261,6 +215,161 @@ pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { } } +/// Builds a close instruction. +pub fn close(signer: Pubkey) -> Instruction { + let proof_pda = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(proof_pda.0, false), + AccountMeta::new_readonly(solana_program::system_program::id(), false), + ], + data: OreInstruction::Close.to_vec(), + } +} + +/// Builds a mine instruction. +pub fn mine(signer: Pubkey, bus: Pubkey, solution: Solution) -> Instruction { + let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(bus, false), + AccountMeta::new_readonly(CONFIG_ADDRESS, false), + AccountMeta::new(proof, false), + AccountMeta::new_readonly(sysvar::instructions::id(), false), + AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), + ], + data: [ + OreInstruction::Mine.to_vec(), + MineArgs { + digest: solution.d, + nonce: solution.n, + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + +/// Builds an open instruction. +pub fn open(signer: Pubkey, miner: Pubkey) -> Instruction { + let proof_pda = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new_readonly(miner, false), + AccountMeta::new(proof_pda.0, false), + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), + ], + data: [ + OreInstruction::Open.to_vec(), + OpenArgs { bump: proof_pda.1 }.to_bytes().to_vec(), + ] + .concat(), + } +} + +/// Builds a reset instruction. +pub fn reset(signer: Pubkey) -> Instruction { + let treasury_tokens = spl_associated_token_account::get_associated_token_address( + &TREASURY_ADDRESS, + &MINT_ADDRESS, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(BUS_ADDRESSES[0], false), + AccountMeta::new(BUS_ADDRESSES[1], false), + AccountMeta::new(BUS_ADDRESSES[2], false), + AccountMeta::new(BUS_ADDRESSES[3], false), + AccountMeta::new(BUS_ADDRESSES[4], false), + AccountMeta::new(BUS_ADDRESSES[5], false), + AccountMeta::new(BUS_ADDRESSES[6], false), + AccountMeta::new(BUS_ADDRESSES[7], false), + AccountMeta::new(CONFIG_ADDRESS, false), + AccountMeta::new(MINT_ADDRESS, false), + AccountMeta::new(TREASURY_ADDRESS, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: OreInstruction::Reset.to_vec(), + } +} + +/// Build a stake instruction. +pub fn stake(signer: Pubkey, sender: Pubkey, amount: u64) -> Instruction { + let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; + let treasury_tokens = spl_associated_token_account::get_associated_token_address( + &TREASURY_ADDRESS, + &MINT_ADDRESS, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(proof, false), + AccountMeta::new(sender, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: [ + OreInstruction::Stake.to_vec(), + StakeArgs { + amount: amount.to_le_bytes(), + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + +// Build an update instruction. +pub fn update(signer: Pubkey, miner: Pubkey) -> Instruction { + let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new_readonly(miner, false), + AccountMeta::new(proof, false), + ], + data: OreInstruction::Update.to_vec(), + } +} + +// Build an upgrade instruction. +pub fn upgrade(signer: Pubkey, beneficiary: Pubkey, sender: Pubkey, amount: u64) -> Instruction { + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(beneficiary, false), + AccountMeta::new(MINT_ADDRESS, false), + AccountMeta::new(MINT_V1_ADDRESS, false), + AccountMeta::new(sender, false), + AccountMeta::new(TREASURY_ADDRESS, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: [ + OreInstruction::Upgrade.to_vec(), + UpgradeArgs { + amount: amount.to_le_bytes(), + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + /// Builds an initialize instruction. pub fn initialize(signer: Pubkey) -> Instruction { let bus_pdas = [ @@ -273,6 +382,7 @@ pub fn initialize(signer: Pubkey) -> Instruction { Pubkey::find_program_address(&[BUS, &[6]], &crate::id()), Pubkey::find_program_address(&[BUS, &[7]], &crate::id()), ]; + let config_pda = Pubkey::find_program_address(&[CONFIG], &crate::id()); let mint_pda = Pubkey::find_program_address(&[MINT, MINT_NOISE.as_slice()], &crate::id()); let treasury_pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); let treasury_tokens = @@ -297,6 +407,7 @@ pub fn initialize(signer: Pubkey) -> Instruction { AccountMeta::new(bus_pdas[5].0, false), AccountMeta::new(bus_pdas[6].0, false), AccountMeta::new(bus_pdas[7].0, false), + AccountMeta::new(config_pda.0, false), AccountMeta::new(metadata_pda.0, false), AccountMeta::new(mint_pda.0, false), AccountMeta::new(treasury_pda.0, false), @@ -318,6 +429,7 @@ pub fn initialize(signer: Pubkey) -> Instruction { bus_5_bump: bus_pdas[5].1, bus_6_bump: bus_pdas[6].1, bus_7_bump: bus_pdas[7].1, + config_bump: config_pda.1, metadata_bump: metadata_pda.1, mint_bump: mint_pda.1, treasury_bump: treasury_pda.1, @@ -328,35 +440,3 @@ pub fn initialize(signer: Pubkey) -> Instruction { .concat(), } } - -/// Builds an update_admin instruction. -pub fn update_admin(signer: Pubkey, new_admin: Pubkey) -> Instruction { - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(TREASURY_ADDRESS, false), - ], - data: [ - OreInstruction::UpdateAdmin.to_vec(), - UpdateAdminArgs { new_admin }.to_bytes().to_vec(), - ] - .concat(), - } -} - -/// Builds an update_difficulty instruction. -pub fn update_difficulty(signer: Pubkey, new_difficulty: Hash) -> Instruction { - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(TREASURY_ADDRESS, false), - ], - data: [ - OreInstruction::UpdateDifficulty.to_vec(), - UpdateDifficultyArgs { new_difficulty }.to_bytes().to_vec(), - ] - .concat(), - } -} diff --git a/core/api/src/lib.rs b/core/api/src/lib.rs new file mode 100644 index 0000000..409cafb --- /dev/null +++ b/core/api/src/lib.rs @@ -0,0 +1,11 @@ +pub mod consts; +pub mod error; +pub mod event; +pub mod instruction; +pub mod state; + +pub(crate) use utils; + +use solana_program::declare_id; + +declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); diff --git a/src/state/bus.rs b/core/api/src/state/bus.rs similarity index 53% rename from src/state/bus.rs rename to core/api/src/state/bus.rs index 849fe36..2eaa557 100644 --- a/src/state/bus.rs +++ b/core/api/src/state/bus.rs @@ -1,27 +1,29 @@ use bytemuck::{Pod, Zeroable}; use shank::ShankAccount; -use crate::{ - impl_account_from_bytes, impl_to_bytes, - utils::{AccountDiscriminator, Discriminator}, -}; +use crate::utils::{impl_account_from_bytes, impl_to_bytes, Discriminator}; + +use super::AccountDiscriminator; /// Bus accounts are responsible for distributing mining rewards. /// There are 8 busses total to minimize write-lock contention and allow for parallel mine operations. -/// Every epoch, the bus account rewards counters are topped up to 0.25 ORE each (2 ORE split amongst 8 busses). #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)] pub struct Bus { /// The ID of the bus account. pub id: u64, - /// The quantity of rewards this bus can issue in the current epoch epoch. + /// The remaining rewards this bus has left to payout in the current epoch. pub rewards: u64, + + /// The rewards this bus would have paid out in the current epoch if there no limit. + /// Used to calculate the updated reward rate. + pub theoretical_rewards: u64, } impl Discriminator for Bus { - fn discriminator() -> AccountDiscriminator { - AccountDiscriminator::Bus + fn discriminator() -> u8 { + AccountDiscriminator::Bus.into() } } diff --git a/core/api/src/state/config.rs b/core/api/src/state/config.rs new file mode 100644 index 0000000..d08e00d --- /dev/null +++ b/core/api/src/state/config.rs @@ -0,0 +1,36 @@ +use bytemuck::{Pod, Zeroable}; +use shank::ShankAccount; +use solana_program::pubkey::Pubkey; + +use crate::utils::{impl_account_from_bytes, impl_to_bytes, Discriminator}; + +use super::AccountDiscriminator; + +/// Config is a singleton account which manages admin configurable variables. +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)] +pub struct Config { + /// The admin authority with permission to update the difficulty. + pub admin: Pubkey, + + /// The base reward rate paid out for a hash of minimum difficulty. + pub base_reward_rate: u64, + + /// The timestamp of the last reset. + pub last_reset_at: i64, + + /// The largest known stake balance on the network. + pub max_stake: u64, + + /// The address of the proof account with the highest stake balance. + pub top_staker: Pubkey, +} + +impl Discriminator for Config { + fn discriminator() -> u8 { + AccountDiscriminator::Config.into() + } +} + +impl_to_bytes!(Config); +impl_account_from_bytes!(Config); diff --git a/core/api/src/state/mod.rs b/core/api/src/state/mod.rs new file mode 100644 index 0000000..e228cac --- /dev/null +++ b/core/api/src/state/mod.rs @@ -0,0 +1,20 @@ +mod bus; +mod config; +mod proof; +mod treasury; + +pub use bus::*; +pub use config::*; +pub use proof::*; +pub use treasury::*; + +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] +pub enum AccountDiscriminator { + Bus = 100, + Config = 101, + Proof = 102, + Treasury = 103, +} diff --git a/core/api/src/state/proof.rs b/core/api/src/state/proof.rs new file mode 100644 index 0000000..83e9dd5 --- /dev/null +++ b/core/api/src/state/proof.rs @@ -0,0 +1,49 @@ +use bytemuck::{Pod, Zeroable}; +use shank::ShankAccount; +use solana_program::pubkey::Pubkey; + +use crate::utils::{impl_account_from_bytes, impl_to_bytes, Discriminator}; + +use super::AccountDiscriminator; + +/// Proof accounts track a miner's current hash, claimable rewards, and lifetime stats. +/// Every miner is allowed one proof account which is required by the program to mine or claim rewards. +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)] +pub struct Proof { + /// The signer authorized to use this proof. + pub authority: Pubkey, + + /// The quantity of tokens this miner has staked or earned. + pub balance: u64, + + /// The current mining challenge. + pub challenge: [u8; 32], + + /// The last hash the miner provided. + pub last_hash: [u8; 32], + + /// The last time this account provided a hash. + pub last_hash_at: i64, + + /// The last time stake was deposited into this account. + pub last_stake_at: i64, + + /// The keypair which has permission to submit hashes for mining. + pub miner: Pubkey, + + /// The total lifetime hashes provided by this miner. + pub total_hashes: u64, + + /// The total lifetime rewards distributed to this miner. + pub total_rewards: u64, +} + +impl Discriminator for Proof { + fn discriminator() -> u8 { + AccountDiscriminator::Proof.into() + } +} + +impl_to_bytes!(Proof); +impl_account_from_bytes!(Proof); diff --git a/core/api/src/state/treasury.rs b/core/api/src/state/treasury.rs new file mode 100644 index 0000000..5b78b6c --- /dev/null +++ b/core/api/src/state/treasury.rs @@ -0,0 +1,21 @@ +use bytemuck::{Pod, Zeroable}; +use shank::ShankAccount; + +use crate::utils::{impl_account_from_bytes, impl_to_bytes, Discriminator}; + +use super::AccountDiscriminator; + +/// Treasury is a singleton account which manages all program wide variables. +/// It is the mint authority for the Ore token and also the authority of the program-owned token account. +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)] +pub struct Treasury {} + +impl Discriminator for Treasury { + fn discriminator() -> u8 { + AccountDiscriminator::Treasury.into() + } +} + +impl_to_bytes!(Treasury); +impl_account_from_bytes!(Treasury); diff --git a/core/program/Cargo.toml b/core/program/Cargo.toml new file mode 100644 index 0000000..cb97a8e --- /dev/null +++ b/core/program/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "ore-program" +description = "ORE is a fair-launch, proof-of-work, digital currency everyone can mine" +version.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true +readme.workspace = true +keywords.workspace = true + +[lib] +crate-type = ["cdylib", "lib"] +name = "ore" + +[features] +no-entrypoint = [] +default = [] + +[dependencies] +drillx.workspace = true +mpl-token-metadata.workspace = true +ore-api = { path = "../api" } +solana-program.workspace = true +spl-token.workspace = true +spl-associated-token-account.workspace = true +utils.workspace = true + +[dev-dependencies] +bs64 = "0.1.2" +rand = "0.8.5" +solana-program-test = "^1.18" +solana-sdk = "^1.18" +tokio = { version = "1.35", features = ["full"] } diff --git a/src/processor/claim.rs b/core/program/src/claim.rs similarity index 59% rename from src/processor/claim.rs rename to core/program/src/claim.rs index 849297a..554f824 100644 --- a/src/processor/claim.rs +++ b/core/program/src/claim.rs @@ -1,26 +1,19 @@ +use ore_api::{consts::*, error::OreError, instruction::ClaimArgs, state::Proof}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, }; -use crate::{ - error::OreError, - instruction::ClaimArgs, - loaders::*, - state::{Proof, Treasury}, - utils::AccountDeserialize, - MINT_ADDRESS, TREASURY, -}; +use crate::{loaders::*, utils::AccountDeserialize}; -/// Claim distributes owed token rewards from the treasury to the miner. Its responsibilies include: -/// 1. Transfer tokens from the treasury to the miner. -/// 2. Decrement the miner's claimable rewards counter by an appropriate amount. -/// 3. Update the program's lifetime stats. +/// Claim distributes Ore from the treasury to a miner. Its responsibilies include: +/// 1. Decrement the miner's claimable balance. +/// 2. Transfer tokens from the treasury to the miner. /// /// Safety requirements: -/// - Claim is a permissionless instruction and can be called by any miner. +/// - Claim is a permissionless instruction and can be called by any user. /// - Can only succeed if the claimed amount is less than or equal to the miner's claimable rewards. -/// - The provided beneficiary token account, treasury, treasury token account, and token program must be valid. +/// - The provided beneficiary, token account, treasury, treasury token account, and token program must be valid. pub fn process_claim<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], @@ -31,15 +24,16 @@ pub fn process_claim<'a, 'info>( let amount = u64::from_le_bytes(args.amount); // Load accounts - let [signer, beneficiary_info, proof_info, treasury_info, treasury_tokens_info, token_program] = + let [signer, beneficiary_info, mint_info, proof_info, treasury_info, treasury_tokens_info, token_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; load_token_account(beneficiary_info, None, &MINT_ADDRESS, true)?; + load_mint(mint_info, MINT_ADDRESS, true)?; load_proof(proof_info, signer.key, true)?; - load_treasury(treasury_info, true)?; + load_treasury(treasury_info, false)?; load_token_account( treasury_tokens_info, Some(treasury_info.key), @@ -48,22 +42,15 @@ pub fn process_claim<'a, 'info>( )?; load_program(token_program, spl_token::id())?; - // Update claimable amount + // Update miner balance let mut proof_data = proof_info.data.borrow_mut(); let proof = Proof::try_from_bytes_mut(&mut proof_data)?; - proof.claimable_rewards = proof - .claimable_rewards + proof.balance = proof + .balance .checked_sub(amount) .ok_or(OreError::ClaimTooLarge)?; - // Update lifetime status - let mut treasury_data = treasury_info.data.borrow_mut(); - let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; - treasury.total_claimed_rewards = treasury.total_claimed_rewards.saturating_add(amount); - // Distribute tokens from treasury to beneficiary - let treasury_bump = treasury.bump; - drop(treasury_data); solana_program::program::invoke_signed( &spl_token::instruction::transfer( &spl_token::id(), @@ -79,7 +66,7 @@ pub fn process_claim<'a, 'info>( beneficiary_info.clone(), treasury_info.clone(), ], - &[&[TREASURY, &[treasury_bump as u8]]], + &[&[TREASURY, &[TREASURY_BUMP]]], )?; Ok(()) diff --git a/core/program/src/close.rs b/core/program/src/close.rs new file mode 100644 index 0000000..107f1c0 --- /dev/null +++ b/core/program/src/close.rs @@ -0,0 +1,46 @@ +use ore_api::state::Proof; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, system_program, +}; + +use crate::{loaders::*, utils::AccountDeserialize}; + +/// Close closes a proof account and returns the rent to the owner. Its responsibilities include: +/// 1. Realloc proof account size to 0. +/// 2. Transfer lamports to the owner. +/// +/// Safety requirements: +/// - Deregister is a permissionless instruction and can be invoked by any singer. +/// - Can only succeed if the provided proof acount PDA is valid (associated with the signer). +/// - The provided system program must be valid. +pub fn process_close<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + _data: &[u8], +) -> ProgramResult { + // Load accounts + let [signer, proof_info, system_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer)?; + load_proof(proof_info, signer.key, true)?; + load_program(system_program, system_program::id())?; + + // Validate balance is zero + let proof_data = proof_info.data.borrow(); + let proof = Proof::try_from_bytes(&proof_data)?; + if proof.balance.gt(&0) { + return Err(ProgramError::InvalidAccountData); + } + drop(proof_data); + + // Realloc data to zero + proof_info.realloc(0, true)?; + + // Send lamports to signer + **signer.lamports.borrow_mut() += proof_info.lamports(); + **proof_info.lamports.borrow_mut() = 0; + + Ok(()) +} diff --git a/core/program/src/crown.rs b/core/program/src/crown.rs new file mode 100644 index 0000000..aeb1827 --- /dev/null +++ b/core/program/src/crown.rs @@ -0,0 +1,52 @@ +use ore_api::state::{Config, Proof}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, +}; + +use crate::{loaders::*, utils::AccountDeserialize}; + +/// Crown flags an account as the top staker if their balance is greater than the last known top staker. +pub fn process_crown<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + _data: &[u8], +) -> ProgramResult { + // Load accounts + let [signer, config_info, proof_info, proof_new_info] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer)?; + load_config(config_info, true)?; + load_any_proof(proof_new_info, false)?; + + // Load config + let mut config_data = config_info.data.borrow_mut(); + let config = Config::try_from_bytes_mut(&mut config_data)?; + + // Load proposed new top staker + let proof_new_data = proof_new_info.data.borrow(); + let proof_new = Proof::try_from_bytes(&proof_new_data)?; + + // If top staker is the defualt null balance, skip this. + if config.top_staker.ne(&Pubkey::new_from_array([0; 32])) { + // Load current top staker + load_any_proof(proof_info, false)?; + if proof_info.key.ne(&config.top_staker) { + return Ok(()); + } + + // Compare balances + let proof_data = proof_info.data.borrow(); + let proof = Proof::try_from_bytes(&proof_data)?; + if proof_new.balance.lt(&proof.balance) { + return Ok(()); + } + } + + // Crown the new top staker + config.max_stake = proof_new.balance; + config.top_staker = *proof_new_info.key; + + Ok(()) +} diff --git a/src/processor/initialize.rs b/core/program/src/initialize.rs similarity index 79% rename from src/processor/initialize.rs rename to core/program/src/initialize.rs index cd29e63..d4200f9 100644 --- a/src/processor/initialize.rs +++ b/core/program/src/initialize.rs @@ -1,5 +1,10 @@ use std::mem::size_of; +use ore_api::{ + consts::*, + instruction::*, + state::{Bus, Config, Treasury}, +}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, @@ -11,14 +16,8 @@ use solana_program::{ use spl_token::state::Mint; use crate::{ - instruction::*, loaders::*, - state::{Bus, Treasury}, - utils::create_pda, - utils::AccountDeserialize, - utils::Discriminator, - BUS, BUS_COUNT, INITIAL_DIFFICULTY, INITIAL_REWARD_RATE, METADATA, METADATA_NAME, - METADATA_SYMBOL, METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, + utils::{create_pda, AccountDeserialize, Discriminator}, }; /// Initialize sets up the Ore program. Its responsibilities include: @@ -48,20 +47,21 @@ pub fn process_initialize<'a, 'info>( let args = InitializeArgs::try_from_bytes(data)?; // Load accounts - let [signer, bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, bus_7_info, metadata_info, mint_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program, metadata_program, rent_sysvar] = + let [signer, bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, bus_7_info, config_info, metadata_info, mint_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program, metadata_program, rent_sysvar] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; - load_uninitialized_pda(bus_0_info, &[BUS, &[0]], args.bus_0_bump, &crate::id())?; - load_uninitialized_pda(bus_1_info, &[BUS, &[1]], args.bus_1_bump, &crate::id())?; - load_uninitialized_pda(bus_2_info, &[BUS, &[2]], args.bus_2_bump, &crate::id())?; - load_uninitialized_pda(bus_3_info, &[BUS, &[3]], args.bus_3_bump, &crate::id())?; - load_uninitialized_pda(bus_4_info, &[BUS, &[4]], args.bus_4_bump, &crate::id())?; - load_uninitialized_pda(bus_5_info, &[BUS, &[5]], args.bus_5_bump, &crate::id())?; - load_uninitialized_pda(bus_6_info, &[BUS, &[6]], args.bus_6_bump, &crate::id())?; - load_uninitialized_pda(bus_7_info, &[BUS, &[7]], args.bus_7_bump, &crate::id())?; + load_uninitialized_pda(bus_0_info, &[BUS, &[0]], args.bus_0_bump, &ore_api::id())?; + load_uninitialized_pda(bus_1_info, &[BUS, &[1]], args.bus_1_bump, &ore_api::id())?; + load_uninitialized_pda(bus_2_info, &[BUS, &[2]], args.bus_2_bump, &ore_api::id())?; + load_uninitialized_pda(bus_3_info, &[BUS, &[3]], args.bus_3_bump, &ore_api::id())?; + load_uninitialized_pda(bus_4_info, &[BUS, &[4]], args.bus_4_bump, &ore_api::id())?; + load_uninitialized_pda(bus_5_info, &[BUS, &[5]], args.bus_5_bump, &ore_api::id())?; + load_uninitialized_pda(bus_6_info, &[BUS, &[6]], args.bus_6_bump, &ore_api::id())?; + load_uninitialized_pda(bus_7_info, &[BUS, &[7]], args.bus_7_bump, &ore_api::id())?; + load_uninitialized_pda(config_info, &[CONFIG], args.config_bump, &ore_api::id())?; load_uninitialized_pda( metadata_info, &[ @@ -76,16 +76,26 @@ pub fn process_initialize<'a, 'info>( mint_info, &[MINT, MINT_NOISE.as_slice()], args.mint_bump, - &crate::id(), + &ore_api::id(), )?; - load_uninitialized_pda(treasury_info, &[TREASURY], args.treasury_bump, &crate::id())?; - load_uninitialized_account(treasury_tokens_info)?; + load_uninitialized_pda( + treasury_info, + &[TREASURY], + args.treasury_bump, + &ore_api::id(), + )?; + load_system_account(treasury_tokens_info, true)?; load_program(system_program, system_program::id())?; load_program(token_program, spl_token::id())?; load_program(associated_token_program, spl_associated_token_account::id())?; load_program(metadata_program, mpl_token_metadata::ID)?; load_sysvar(rent_sysvar, sysvar::rent::id())?; + // Check signer + if signer.key.ne(&INITIAL_ADMIN) { + return Err(ProgramError::MissingRequiredSignature); + } + // Initialize bus accounts let bus_infos = [ bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, @@ -104,7 +114,7 @@ pub fn process_initialize<'a, 'info>( for i in 0..BUS_COUNT { create_pda( bus_infos[i], - &crate::id(), + &ore_api::id(), 8 + size_of::(), &[BUS, &[i as u8], &[bus_bumps[i]]], system_program, @@ -117,10 +127,28 @@ pub fn process_initialize<'a, 'info>( bus.rewards = 0; } + // Initialize config + create_pda( + config_info, + &ore_api::id(), + 8 + size_of::(), + &[CONFIG, &[args.config_bump]], + system_program, + signer, + )?; + let mut config_data = config_info.data.borrow_mut(); + config_data[0] = Config::discriminator() as u8; + let config = Config::try_from_bytes_mut(&mut config_data)?; + config.admin = *signer.key; + config.base_reward_rate = INITIAL_BASE_REWARD_RATE; + config.last_reset_at = 0; + config.max_stake = 0; + config.top_staker = Pubkey::new_from_array([0; 32]); + // Initialize treasury create_pda( treasury_info, - &crate::id(), + &ore_api::id(), 8 + size_of::(), &[TREASURY, &[args.treasury_bump]], system_program, @@ -128,13 +156,6 @@ pub fn process_initialize<'a, 'info>( )?; let mut treasury_data = treasury_info.data.borrow_mut(); treasury_data[0] = Treasury::discriminator() as u8; - let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; - treasury.admin = *signer.key; - treasury.bump = args.treasury_bump as u64; - treasury.difficulty = INITIAL_DIFFICULTY.into(); - treasury.last_reset_at = 0; - treasury.reward_rate = INITIAL_REWARD_RATE; - treasury.total_claimed_rewards = 0; drop(treasury_data); // Initialize mint diff --git a/core/program/src/lib.rs b/core/program/src/lib.rs new file mode 100644 index 0000000..bac4a88 --- /dev/null +++ b/core/program/src/lib.rs @@ -0,0 +1,62 @@ +mod claim; +mod close; +mod crown; +mod initialize; +mod loaders; +mod mine; +mod open; +mod reset; +mod stake; +mod update; +mod upgrade; + +use claim::*; +use close::*; +use crown::*; +use initialize::*; +use mine::*; +use open::*; +use reset::*; +use stake::*; +use update::*; +use upgrade::*; + +use ore_api::instruction::*; +use solana_program::{ + self, account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, +}; + +pub(crate) use utils; + +#[cfg(not(feature = "no-entrypoint"))] +solana_program::entrypoint!(process_instruction); + +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + if program_id.ne(&ore_api::id()) { + return Err(ProgramError::IncorrectProgramId); + } + + let (tag, data) = data + .split_first() + .ok_or(ProgramError::InvalidInstructionData)?; + + match OreInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))? { + OreInstruction::Claim => process_claim(program_id, accounts, data)?, + OreInstruction::Close => process_close(program_id, accounts, data)?, + OreInstruction::Crown => process_crown(program_id, accounts, data)?, + OreInstruction::Mine => process_mine(program_id, accounts, data)?, + OreInstruction::Open => process_open(program_id, accounts, data)?, + OreInstruction::Reset => process_reset(program_id, accounts, data)?, + OreInstruction::Stake => process_stake(program_id, accounts, data)?, + OreInstruction::Update => process_update(program_id, accounts, data)?, + OreInstruction::Upgrade => process_upgrade(program_id, accounts, data)?, + OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, + } + + Ok(()) +} diff --git a/core/program/src/loaders.rs b/core/program/src/loaders.rs new file mode 100644 index 0000000..1daaa96 --- /dev/null +++ b/core/program/src/loaders.rs @@ -0,0 +1,425 @@ +use ore_api::{ + consts::*, + state::{Bus, Config, Proof, Treasury}, +}; +use solana_program::{ + account_info::AccountInfo, program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, + system_program, sysvar, +}; +use spl_token::state::Mint; + +use crate::utils::{AccountDeserialize, Discriminator}; + +/// Errors if: +/// - Account is not a signer. +pub fn load_signer<'a, 'info>(info: &'a AccountInfo<'info>) -> Result<(), ProgramError> { + if !info.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + + Ok(()) +} + +/// Errors if: +/// - Owner is not Ore program. +/// - Address does not match the expected bus address. +/// - Data is empty. +/// - Data cannot deserialize into a bus account. +/// - Bus ID does not match the expected ID. +/// - Expected to be writable, but is not. +pub fn load_bus<'a, 'info>( + info: &'a AccountInfo<'info>, + id: u64, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&ore_api::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if info.key.ne(&BUS_ADDRESSES[id as usize]) { + return Err(ProgramError::InvalidSeeds); + } + + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + let bus_data = info.data.borrow(); + let bus = Bus::try_from_bytes(&bus_data)?; + + if bus.id.ne(&id) { + return Err(ProgramError::InvalidAccountData); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + +/// Errors if: +/// - Owner is not Ore program. +/// - Data is empty. +/// - Data cannot deserialize into a bus account. +/// - Bus ID is not in the expected range. +/// - Address is not in set of valid bus address. +/// - Expected to be writable, but is not. +pub fn load_any_bus<'a, 'info>( + info: &'a AccountInfo<'info>, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&ore_api::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + if info.data.borrow()[0].ne(&(Bus::discriminator() as u8)) { + return Err(solana_program::program_error::ProgramError::InvalidAccountData); + } + + if !BUS_ADDRESSES.contains(info.key) { + return Err(ProgramError::InvalidSeeds); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + +/// Errors if: +/// - Owner is not Ore program. +/// - Address does not match the expected address. +/// - Data is empty. +/// - Data cannot deserialize into a config account. +/// - Expected to be writable, but is not. +pub fn load_config<'a, 'info>( + info: &'a AccountInfo<'info>, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&ore_api::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if info.key.ne(&CONFIG_ADDRESS) { + return Err(ProgramError::InvalidSeeds); + } + + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + if info.data.borrow()[0].ne(&(Config::discriminator() as u8)) { + return Err(solana_program::program_error::ProgramError::InvalidAccountData); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + +/// Errors if: +/// - Owner is not Ore program. +/// - Data is empty. +/// - Data cannot deserialize into a proof account. +/// - Proof authority does not match the expected address. +/// - Expected to be writable, but is not. +pub fn load_proof<'a, 'info>( + info: &'a AccountInfo<'info>, + authority: &Pubkey, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&ore_api::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + let proof_data = info.data.borrow(); + let proof = Proof::try_from_bytes(&proof_data)?; + + if proof.authority.ne(&authority) { + return Err(ProgramError::InvalidAccountData); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + +/// Errors if: +/// - Owner is not Ore program. +/// - Data is empty. +/// - Data cannot deserialize into a proof account. +/// - Proof miner does not match the expected address. +/// - Expected to be writable, but is not. +pub fn load_proof_with_miner<'a, 'info>( + info: &'a AccountInfo<'info>, + miner: &Pubkey, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&ore_api::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + let proof_data = info.data.borrow(); + let proof = Proof::try_from_bytes(&proof_data)?; + + if proof.miner.ne(&miner) { + return Err(ProgramError::InvalidAccountData); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + +/// Errors if: +/// - Owner is not Ore program. +/// - Data is empty. +/// - Data cannot deserialize into a proof account. +/// - Expected to be writable, but is not. +pub fn load_any_proof<'a, 'info>( + info: &'a AccountInfo<'info>, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&ore_api::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + if info.data.borrow()[0].ne(&(Proof::discriminator() as u8)) { + return Err(solana_program::program_error::ProgramError::InvalidAccountData); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + +/// Errors if: +/// - Owner is not Ore program. +/// - Address does not match the expected address. +/// - Data is empty. +/// - Data cannot deserialize into a treasury account. +/// - Expected to be writable, but is not. +pub fn load_treasury<'a, 'info>( + info: &'a AccountInfo<'info>, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&ore_api::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if info.key.ne(&TREASURY_ADDRESS) { + return Err(ProgramError::InvalidSeeds); + } + + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + if info.data.borrow()[0].ne(&(Treasury::discriminator() as u8)) { + return Err(solana_program::program_error::ProgramError::InvalidAccountData); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + +/// Errors if: +/// - Owner is not SPL token program. +/// - Address does not match the expected mint address. +/// - Data is empty. +/// - Data cannot deserialize into a mint account. +/// - Expected to be writable, but is not. +pub fn load_mint<'a, 'info>( + info: &'a AccountInfo<'info>, + address: Pubkey, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&spl_token::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if info.key.ne(&address) { + return Err(ProgramError::InvalidSeeds); + } + + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + if Mint::unpack_unchecked(&info.data.borrow()).is_err() { + return Err(ProgramError::InvalidAccountData); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + +/// Errors if: +/// - Owner is not SPL token program. +/// - Data is empty. +/// - Data cannot deserialize into a token account. +/// - Token account owner does not match the expected owner address. +/// - Token account mint does not match the expected mint address. +/// - Expected to be writable, but is not. +pub fn load_token_account<'a, 'info>( + info: &'a AccountInfo<'info>, + owner: Option<&Pubkey>, + mint: &Pubkey, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&spl_token::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + let account_data = info.data.borrow(); + let account = spl_token::state::Account::unpack_unchecked(&account_data) + .or(Err(ProgramError::InvalidAccountData))?; + + if account.mint.ne(&mint) { + return Err(ProgramError::InvalidAccountData); + } + + if let Some(owner) = owner { + if account.owner.ne(owner) { + return Err(ProgramError::InvalidAccountData); + } + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + +/// Errors if: +/// - Address does not match PDA derived from provided seeds. +/// - Cannot load as an uninitialized account. +pub fn load_uninitialized_pda<'a, 'info>( + info: &'a AccountInfo<'info>, + seeds: &[&[u8]], + bump: u8, + program_id: &Pubkey, +) -> Result<(), ProgramError> { + let pda = Pubkey::find_program_address(seeds, program_id); + + if info.key.ne(&pda.0) { + return Err(ProgramError::InvalidSeeds); + } + + if bump.ne(&pda.1) { + return Err(ProgramError::InvalidSeeds); + } + + load_system_account(info, true) +} + +/// Errors if: +/// - Owner is not the system program. +/// - Data is not empty. +/// - Account is not writable. +pub fn load_system_account<'a, 'info>( + info: &'a AccountInfo<'info>, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&system_program::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if !info.data_is_empty() { + return Err(ProgramError::AccountAlreadyInitialized); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + +/// Errors if: +/// - Owner is not the sysvar address. +/// - Account cannot load with the expected address. +pub fn load_sysvar<'a, 'info>( + info: &'a AccountInfo<'info>, + key: Pubkey, +) -> Result<(), ProgramError> { + if info.owner.ne(&sysvar::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + load_account(info, key, false) +} + +/// Errors if: +/// - Address does not match the expected value. +/// - Expected to be writable, but is not. +pub fn load_account<'a, 'info>( + info: &'a AccountInfo<'info>, + key: Pubkey, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.key.ne(&key) { + return Err(ProgramError::InvalidAccountData); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + +/// Errors if: +/// - Address does not match the expected value. +/// - Account is not executable. +pub fn load_program<'a, 'info>( + info: &'a AccountInfo<'info>, + key: Pubkey, +) -> Result<(), ProgramError> { + if info.key.ne(&key) { + return Err(ProgramError::IncorrectProgramId); + } + + if !info.executable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} diff --git a/core/program/src/mine.rs b/core/program/src/mine.rs new file mode 100644 index 0000000..d362ed0 --- /dev/null +++ b/core/program/src/mine.rs @@ -0,0 +1,220 @@ +use std::mem::size_of; + +use drillx::Solution; +use ore_api::{ + consts::*, + error::OreError, + event::MineEvent, + instruction::{MineArgs, OreInstruction}, + state::{Bus, Config, Proof}, +}; +use solana_program::program::set_return_data; +#[allow(deprecated)] +use solana_program::{ + account_info::AccountInfo, + blake3::hashv, + clock::Clock, + entrypoint::ProgramResult, + log::sol_log, + program_error::ProgramError, + pubkey, + pubkey::Pubkey, + sanitize::SanitizeError, + serialize_utils::{read_pubkey, read_u16, read_u8}, + slot_hashes::SlotHash, + sysvar::{self, instructions::load_current_index, Sysvar}, +}; + +use crate::{loaders::*, utils::AccountDeserialize}; + +/// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: +/// 1. Calculate the hash from the provided nonce. +/// 2. Payout rewards based on difficulty, staking multiplier, and liveness penalty. +/// 3. Generate a new challenge for the miner. +/// 4. Update the miner's lifetime stats. +/// +/// Safety requirements: +/// - Mine is a permissionless instruction and can be called by any signer. +/// - Can only succeed if mining is not paused. +/// - Can only succeed if the last reset was less than 60 seconds ago. +/// - Can only succeed if the provided hash satisfies the minimum difficulty requirement. +/// - The the provided proof account must be associated with the signer. +/// - The provided bus, config, noise, stake, and slot hash sysvar must be valid. +pub fn process_mine<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + data: &[u8], +) -> ProgramResult { + // Parse args + let args = MineArgs::try_from_bytes(data)?; + + // Load accounts + let [signer, bus_info, config_info, proof_info, instructions_sysvar, slot_hashes_sysvar] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer)?; + load_any_bus(bus_info, true)?; + load_config(config_info, false)?; + load_proof_with_miner(proof_info, signer.key, true)?; + load_sysvar(instructions_sysvar, sysvar::instructions::id())?; + load_sysvar(slot_hashes_sysvar, sysvar::slot_hashes::id())?; + + // Validate this is the only mine ix in the transaction. + if !validate_transaction(&instructions_sysvar.data.borrow()).unwrap_or(false) { + return Err(OreError::TransactionInvalid.into()); + } + + // Validate epoch is active. + let config_data = config_info.data.borrow(); + let config = Config::try_from_bytes(&config_data)?; + let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; + if config + .last_reset_at + .saturating_add(EPOCH_DURATION) + .le(&clock.unix_timestamp) + { + return Err(OreError::NeedsReset.into()); + } + + // Validate the hash digest. + let mut proof_data = proof_info.data.borrow_mut(); + let proof = Proof::try_from_bytes_mut(&mut proof_data)?; + let solution = Solution::new(args.digest, args.nonce); + if !solution.is_valid(&proof.challenge) { + return Err(OreError::HashInvalid.into()); + } + + // Validate hash satisfies the minimnum difficulty. + let hash = solution.to_hash(); + let difficulty = hash.difficulty(); + sol_log(&format!("Diff {}", difficulty)); + if difficulty.lt(&MIN_DIFFICULTY) { + return Err(OreError::HashTooEasy.into()); + } + + // Calculate base reward rate. + let difficulty = difficulty.saturating_sub(MIN_DIFFICULTY); + let mut reward = config + .base_reward_rate + .saturating_mul(2u64.saturating_pow(difficulty)); + + // Apply staking multiplier. + // If user has greater than or equal to the max stake on the network, they receive 2x multiplier. + // Any stake less than this will receives between 1x and 2x multipler. The multipler is only active + // if the miner's last stake deposit was more than one minute ago. + if config.max_stake.gt(&0) + && proof + .last_stake_at + .saturating_add(ONE_MINUTE) + .le(&clock.unix_timestamp) + { + let staking_reward = proof + .balance + .min(config.max_stake) + .saturating_mul(reward) + .saturating_div(config.max_stake); + reward = reward.saturating_add(staking_reward); + } + + // Reject spam transactions. + let t = clock.unix_timestamp; + let t_target = proof.last_hash_at.saturating_add(ONE_MINUTE); + let t_spam = t_target.saturating_sub(TOLERANCE); + if t.lt(&t_spam) { + return Err(OreError::Spam.into()); + } + + // Apply liveness penalty. + let t_liveness = t_target.saturating_add(TOLERANCE); + if t.gt(&t_liveness) { + reward = reward.saturating_sub( + reward + .saturating_mul(t.saturating_sub(t_liveness) as u64) + .saturating_div(ONE_MINUTE as u64), + ); + } + + // Limit payout amount to whatever is left in the bus + let mut bus_data = bus_info.data.borrow_mut(); + let bus = Bus::try_from_bytes_mut(&mut bus_data)?; + let reward_actual = reward.min(bus.rewards); + + // Update balances + bus.theoretical_rewards = bus.theoretical_rewards.saturating_add(reward); + bus.rewards = bus.rewards.saturating_sub(reward_actual); + proof.balance = proof.balance.saturating_add(reward_actual); + + // Hash recent slot hash into the next challenge to prevent pre-mining attacks + proof.last_hash = hash.h; + proof.challenge = hashv(&[ + hash.h.as_slice(), + &slot_hashes_sysvar.data.borrow()[0..size_of::()], + ]) + .0; + + // Update time trackers + proof.last_hash_at = clock.unix_timestamp; + + // Update lifetime stats + proof.total_hashes = proof.total_hashes.saturating_add(1); + proof.total_rewards = proof.total_rewards.saturating_add(reward); + + // Log the mined rewards + set_return_data( + MineEvent { + difficulty: difficulty as u64, + reward: reward_actual, + timing: t.saturating_sub(t_liveness), + } + .to_bytes(), + ); + + Ok(()) +} + +/// Require that there is only one `mine` instruction per transaction and it is called from the +/// top level of the transaction. +/// +/// The intent here is to disincentivize sybil. As long as a user can fit multiple hashes in a single +/// transaction, there is a financial incentive to sybil multiple keypairs and pack as many hashes +/// as possible into each transaction to minimize fee / hash. +/// +/// If each transaction is limited to one hash only, then a user will minimize their fee / hash +/// by allocating all their hashpower to finding the single most difficult hash they can. +fn validate_transaction(msg: &[u8]) -> Result { + #[allow(deprecated)] + let idx = load_current_index(msg); + let mut c = 0; + let num_instructions = read_u16(&mut c, msg)?; + let pc = c; + for i in 0..num_instructions as usize { + c = pc + i * 2; + c = read_u16(&mut c, msg)? as usize; + let num_accounts = read_u16(&mut c, msg)? as usize; + c += num_accounts * 33; + // Only allow instructions to call ore and the compute budget program. + match read_pubkey(&mut c, msg)? { + ore_api::ID => { + c += 2; + if let Ok(ix) = OreInstruction::try_from(read_u8(&mut c, msg)?) { + if let OreInstruction::Mine = ix { + if i.ne(&(idx as usize)) { + return Ok(false); + } + } + } else { + return Ok(false); + } + } + COMPUTE_BUDGET_PROGRAM_ID => {} // Noop + _ => return Ok(false), + } + } + + Ok(true) +} + +/// Program id of the compute budge program. +const COMPUTE_BUDGET_PROGRAM_ID: Pubkey = pubkey!("ComputeBudget111111111111111111111111111111"); diff --git a/src/processor/register.rs b/core/program/src/open.rs similarity index 54% rename from src/processor/register.rs rename to core/program/src/open.rs index 94fc9fd..66e603a 100644 --- a/src/processor/register.rs +++ b/core/program/src/open.rs @@ -1,17 +1,21 @@ use std::mem::size_of; +use ore_api::{consts::*, instruction::OpenArgs, state::Proof}; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, keccak::hashv, - program_error::ProgramError, pubkey::Pubkey, system_program, + account_info::AccountInfo, + blake3::hashv, + clock::Clock, + entrypoint::ProgramResult, + program_error::ProgramError, + pubkey::Pubkey, + slot_hashes::SlotHash, + system_program, + sysvar::{self, Sysvar}, }; use crate::{ - instruction::RegisterArgs, loaders::*, - state::Proof, - utils::AccountDeserialize, - utils::{create_pda, Discriminator}, - PROOF, + utils::{create_pda, AccountDeserialize, Discriminator}, }; /// Register generates a new hash chain for a prospective miner. Its responsibilities include: @@ -21,44 +25,55 @@ use crate::{ /// Safety requirements: /// - Register is a permissionless instruction and can be invoked by any singer. /// - Can only succeed if the provided proof acount PDA is valid (associated with the signer). -/// - Can only succeed once per signer. +/// - Can only succeed if the user does not already have a proof account. /// - The provided system program must be valid. -pub fn process_register<'a, 'info>( +pub fn process_open<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], data: &[u8], ) -> ProgramResult { // Parse args - let args = RegisterArgs::try_from_bytes(data)?; + let args = OpenArgs::try_from_bytes(data)?; // Load accounts - let [signer, proof_info, system_program] = accounts else { + let [signer, miner_info, proof_info, system_program, slot_hashes_info] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; + load_system_account(miner_info, false)?; load_uninitialized_pda( proof_info, &[PROOF, signer.key.as_ref()], args.bump, - &crate::id(), + &ore_api::id(), )?; load_program(system_program, system_program::id())?; + load_sysvar(slot_hashes_info, sysvar::slot_hashes::id())?; // Initialize proof create_pda( proof_info, - &crate::id(), + &ore_api::id(), 8 + size_of::(), &[PROOF, signer.key.as_ref(), &[args.bump]], system_program, signer, )?; + let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; let mut proof_data = proof_info.data.borrow_mut(); proof_data[0] = Proof::discriminator() as u8; let proof = Proof::try_from_bytes_mut(&mut proof_data)?; proof.authority = *signer.key; - proof.claimable_rewards = 0; - proof.hash = hashv(&[signer.key.as_ref()]).into(); + proof.balance = 0; + proof.challenge = hashv(&[ + signer.key.as_ref(), + &slot_hashes_info.data.borrow()[0..size_of::()], + ]) + .0; + proof.last_hash = [0; 32]; + proof.last_hash_at = clock.unix_timestamp; + proof.last_stake_at = clock.unix_timestamp; + proof.miner = *miner_info.key; proof.total_hashes = 0; proof.total_rewards = 0; diff --git a/src/processor/reset.rs b/core/program/src/reset.rs similarity index 75% rename from src/processor/reset.rs rename to core/program/src/reset.rs index 68b961c..dd656a3 100644 --- a/src/processor/reset.rs +++ b/core/program/src/reset.rs @@ -1,21 +1,26 @@ +use ore_api::{ + consts::*, + error::OreError, + state::{Bus, Config}, +}; use solana_program::{ account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, - program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, + program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, sysvar::Sysvar, }; +use spl_token::state::Mint; use crate::{ - error::OreError, - loaders::*, - state::{Bus, Treasury}, + loaders::{ + load_bus, load_config, load_mint, load_program, load_signer, load_token_account, + load_treasury, + }, utils::AccountDeserialize, - BUS_COUNT, BUS_EPOCH_REWARDS, EPOCH_DURATION, MAX_EPOCH_REWARDS, SMOOTHING_FACTOR, START_AT, - TARGET_EPOCH_REWARDS, TREASURY, }; /// Reset sets up the Ore program for the next epoch. Its responsibilities include: /// 1. Reset bus account rewards counters. /// 2. Adjust the reward rate to stabilize inflation. -/// 3. Top up the treasury token account to backup claims. +/// 3. Top up the treasury token account to fund claims. /// /// Safety requirements: /// - Reset is a permissionless instruction and can be invoked by any signer. @@ -26,15 +31,18 @@ use crate::{ /// Discussion: /// - It is important that `reset` can only be invoked once per 60 second period to ensure the supply growth rate /// stays within the guaranteed bounds of 0 ≤ R ≤ 2 ORE/min. -/// - The reward rate is dynamically adjusted based on last epoch's actual reward rate (proxy for hashpower) to -/// target an average supply growth rate of 1 ORE/min. +/// - The reward rate is dynamically adjusted based on last epoch's theoretical reward rate to target an average +/// supply growth rate of 1 ORE/min. +/// - The "theoretical" reward rate refers to the amount that would have been paid out if rewards were not capped by +/// the bus limits. It's necessary to use this value to ensure the reward rate update calculation accurately +/// accounts for the difficulty of submitted hashes. pub fn process_reset<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], _data: &[u8], ) -> ProgramResult { // Load accounts - let [signer, bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, bus_7_info, mint_info, treasury_info, treasury_tokens_info, token_program] = + let [signer, bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, bus_7_info, config_info, mint_info, treasury_info, treasury_tokens_info, token_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -48,7 +56,8 @@ pub fn process_reset<'a, 'info>( load_bus(bus_5_info, 5, true)?; load_bus(bus_6_info, 6, true)?; load_bus(bus_7_info, 7, true)?; - load_mint(mint_info, true)?; + load_config(config_info, true)?; + load_mint(mint_info, MINT_ADDRESS, true)?; load_treasury(treasury_info, true)?; load_token_account( treasury_tokens_info, @@ -62,39 +71,49 @@ pub fn process_reset<'a, 'info>( bus_7_info, ]; - // Validate mining has starting + // Validate enough time has passed since last reset + let mut config_data = config_info.data.borrow_mut(); + let config = Config::try_from_bytes_mut(&mut config_data)?; let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; - if clock.unix_timestamp.lt(&START_AT) { - return Err(OreError::NotStarted.into()); + if config + .last_reset_at + .saturating_add(EPOCH_DURATION) + .gt(&clock.unix_timestamp) + { + return Ok(()); } - // Validate at least 60 seconds have passed since last reset - let mut treasury_data = treasury_info.data.borrow_mut(); - let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; - let threshold = treasury.last_reset_at.saturating_add(EPOCH_DURATION); - if clock.unix_timestamp.lt(&threshold) { - return Err(OreError::ResetTooEarly.into()); - } - - // Record current timestamp - treasury.last_reset_at = clock.unix_timestamp; + // Update reset timestamp + config.last_reset_at = clock.unix_timestamp; // Reset bus accounts and calculate actual rewards mined since last reset let mut total_remaining_rewards = 0u64; + let mut total_theoretical_rewards = 0u64; for i in 0..BUS_COUNT { let mut bus_data = busses[i].data.borrow_mut(); let bus = Bus::try_from_bytes_mut(&mut bus_data)?; total_remaining_rewards = total_remaining_rewards.saturating_add(bus.rewards); + total_theoretical_rewards = + total_theoretical_rewards.saturating_add(bus.theoretical_rewards); bus.rewards = BUS_EPOCH_REWARDS; + bus.theoretical_rewards = 0; } let total_epoch_rewards = MAX_EPOCH_REWARDS.saturating_sub(total_remaining_rewards); - // Update reward rate for next epoch - treasury.reward_rate = calculate_new_reward_rate(treasury.reward_rate, total_epoch_rewards); + // Update base reward rate for next epoch + config.base_reward_rate = + calculate_new_reward_rate(config.base_reward_rate, total_theoretical_rewards); + + // Max supply check + let mint = Mint::unpack(&mint_info.data.borrow()).expect("Failed to parse mint"); + if mint.supply.ge(&MAX_SUPPLY) { + return Err(OreError::MaxSupply.into()); + } // Fund treasury token account - let treasury_bump = treasury.bump as u8; - drop(treasury_data); + let amount = MAX_SUPPLY + .saturating_sub(mint.supply) + .min(total_epoch_rewards); solana_program::program::invoke_signed( &spl_token::instruction::mint_to( &spl_token::id(), @@ -102,7 +121,7 @@ pub fn process_reset<'a, 'info>( treasury_tokens_info.key, treasury_info.key, &[treasury_info.key], - total_epoch_rewards, + amount, )?, &[ token_program.clone(), @@ -110,7 +129,7 @@ pub fn process_reset<'a, 'info>( treasury_tokens_info.clone(), treasury_info.clone(), ], - &[&[TREASURY, &[treasury_bump]]], + &[&[TREASURY, &[TREASURY_BUMP]]], )?; Ok(()) @@ -132,9 +151,9 @@ pub(crate) fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) - } // Calculate new reward rate. - let new_rate = (current_rate) - .saturating_mul(TARGET_EPOCH_REWARDS) - .saturating_div(epoch_rewards) as u64; + let new_rate = (current_rate as u128) + .saturating_mul(TARGET_EPOCH_REWARDS as u128) + .saturating_div(epoch_rewards as u128) as u64; // Smooth reward rate so it cannot change by more than a constant factor from one epoch to the next. let new_rate_min = current_rate.saturating_div(SMOOTHING_FACTOR); @@ -149,9 +168,9 @@ pub(crate) fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) - mod tests { use rand::{distributions::Uniform, Rng}; - use crate::{ - calculate_new_reward_rate, BUS_EPOCH_REWARDS, MAX_EPOCH_REWARDS, SMOOTHING_FACTOR, - TARGET_EPOCH_REWARDS, + use crate::calculate_new_reward_rate; + use ore_api::consts::{ + BUS_EPOCH_REWARDS, MAX_EPOCH_REWARDS, SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS, }; const FUZZ_SIZE: u64 = 10_000; diff --git a/core/program/src/stake.rs b/core/program/src/stake.rs new file mode 100644 index 0000000..d72ddb3 --- /dev/null +++ b/core/program/src/stake.rs @@ -0,0 +1,69 @@ +use ore_api::{consts::*, instruction::StakeArgs, state::Proof}; +use solana_program::{ + account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, + program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, +}; + +use crate::{loaders::*, utils::AccountDeserialize}; + +/// Stake deposits Ore into a miner's proof account to earn multiplier. Its responsibilies include: +/// 1. Transfer tokens from the miner to the treasury account. +/// 2. Increment the miner's claimable balance. +/// +/// Safety requirements: +/// - Stake is a permissionless instruction and can be called by any user. +/// - Can only succeed if the amount is less than or equal to the miner's transferable tokens. +/// - The provided beneficiary, proof, sender, treasury token account, and token program must be valid. +pub fn process_stake<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + data: &[u8], +) -> ProgramResult { + // Parse args + let args = StakeArgs::try_from_bytes(data)?; + let amount = u64::from_le_bytes(args.amount); + + // Load accounts + let [signer, proof_info, sender_info, treasury_tokens_info, token_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer)?; + load_proof(proof_info, signer.key, true)?; + load_token_account(sender_info, Some(signer.key), &MINT_ADDRESS, true)?; + load_token_account( + treasury_tokens_info, + Some(&TREASURY_ADDRESS), + &MINT_ADDRESS, + true, + )?; + load_program(token_program, spl_token::id())?; + + // Update proof balance + let mut proof_data = proof_info.data.borrow_mut(); + let proof = Proof::try_from_bytes_mut(&mut proof_data)?; + proof.balance = proof.balance.saturating_add(amount); + + // Update deposit timestamp + let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; + proof.last_stake_at = clock.unix_timestamp; + + // Distribute tokens from signer to treasury + solana_program::program::invoke( + &spl_token::instruction::transfer( + &spl_token::id(), + sender_info.key, + treasury_tokens_info.key, + signer.key, + &[signer.key], + amount, + )?, + &[ + token_program.clone(), + sender_info.clone(), + treasury_tokens_info.clone(), + signer.clone(), + ], + )?; + + Ok(()) +} diff --git a/core/program/src/update.rs b/core/program/src/update.rs new file mode 100644 index 0000000..35f384c --- /dev/null +++ b/core/program/src/update.rs @@ -0,0 +1,29 @@ +use ore_api::state::Proof; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, +}; + +use crate::{loaders::*, utils::AccountDeserialize}; + +/// Update changes the miner authority on a proof account. +pub fn process_update<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + _data: &[u8], +) -> ProgramResult { + // Load accounts + let [signer, miner_info, proof_info] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer)?; + load_system_account(miner_info, false)?; + load_proof(proof_info, signer.key, true)?; + + // Update the proof + let mut proof_data = proof_info.data.borrow_mut(); + let proof = Proof::try_from_bytes_mut(&mut proof_data)?; + proof.miner = *miner_info.key; + + Ok(()) +} diff --git a/core/program/src/upgrade.rs b/core/program/src/upgrade.rs new file mode 100644 index 0000000..0f5a044 --- /dev/null +++ b/core/program/src/upgrade.rs @@ -0,0 +1,89 @@ +use ore_api::{consts::*, error::OreError, instruction::StakeArgs}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + program_pack::Pack, pubkey::Pubkey, +}; +use spl_token::state::Mint; + +use crate::loaders::*; + +/// Upgrade allows a user to migrate a v1 token to a v2 token one-for-one. Its responsibilies include: +/// 1. Burns the v1 tokens. +/// 2. Mints an equivalent number of v2 tokens to the user. +/// +/// Safety requirements: +/// - Upgrade is a permissionless instruction and can be called by any user. +/// - The provided beneficiary, mint, mint v1, sender, and token program must be valid. +pub fn process_upgrade<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + data: &[u8], +) -> ProgramResult { + // Parse args + let args = StakeArgs::try_from_bytes(data)?; + let amount = u64::from_le_bytes(args.amount); + + // Load accounts + let [signer, beneficiary_info, mint_info, mint_v1_info, sender_info, treasury_info, token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer)?; + load_token_account(beneficiary_info, Some(&signer.key), &MINT_ADDRESS, true)?; + load_mint(mint_info, MINT_ADDRESS, true)?; + load_mint(mint_v1_info, MINT_V1_ADDRESS, true)?; + load_token_account(sender_info, Some(signer.key), &MINT_V1_ADDRESS, true)?; + load_program(token_program, spl_token::id())?; + + // Burn v1 tokens + solana_program::program::invoke( + &spl_token::instruction::burn( + &spl_token::id(), + sender_info.key, + mint_v1_info.key, + signer.key, + &[signer.key], + amount, + )?, + &[ + token_program.clone(), + sender_info.clone(), + mint_v1_info.clone(), + signer.clone(), + ], + )?; + + // Account for decimals change. + // v1 token has 9 decimals. v2 token has 11. + let amount_to_mint = amount.saturating_mul(100); + + // Cap at max supply. + let mint_data = mint_info.data.borrow(); + let mint = Mint::unpack(&mint_data)?; + if mint.supply.saturating_add(amount_to_mint).gt(&MAX_SUPPLY) { + return Err(OreError::MaxSupply.into()); + } + drop(mint_data); + + // Mint to the beneficiary account + solana_program::program::invoke_signed( + &spl_token::instruction::mint_to( + &spl_token::id(), + mint_info.key, + beneficiary_info.key, + treasury_info.key, + &[treasury_info.key], + amount_to_mint, + )?, + &[ + token_program.clone(), + mint_info.clone(), + beneficiary_info.clone(), + treasury_info.clone(), + ], + &[&[TREASURY, &[TREASURY_BUMP]]], + )?; + + Ok(()) +} diff --git a/idl/ore.json b/idl/ore.json deleted file mode 100644 index 2157a96..0000000 --- a/idl/ore.json +++ /dev/null @@ -1,618 +0,0 @@ -{ - "version": "0.0.1", - "name": "ore", - "instructions": [ - { - "name": "Reset", - "accounts": [ - { - "name": "oreProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore program" - ] - }, - { - "name": "signer", - "isMut": false, - "isSigner": true, - "docs": [ - "Signer" - ] - }, - { - "name": "bus0", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 0" - ] - }, - { - "name": "bus1", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 1" - ] - }, - { - "name": "bus2", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 2" - ] - }, - { - "name": "bus3", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 3" - ] - }, - { - "name": "bus4", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 4" - ] - }, - { - "name": "bus5", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 5" - ] - }, - { - "name": "bus6", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 6" - ] - }, - { - "name": "bus7", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 7" - ] - }, - { - "name": "mint", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore token mint account" - ] - }, - { - "name": "treasury", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore treasury account" - ] - }, - { - "name": "treasuryTokens", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore treasury token account" - ] - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "SPL token program" - ] - } - ], - "args": [], - "discriminant": { - "type": "u8", - "value": 0 - } - }, - { - "name": "Register", - "accounts": [ - { - "name": "oreProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore program" - ] - }, - { - "name": "signer", - "isMut": false, - "isSigner": true, - "docs": [ - "Signer" - ] - }, - { - "name": "proof", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore miner proof account" - ] - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Solana system program" - ] - } - ], - "args": [], - "discriminant": { - "type": "u8", - "value": 1 - } - }, - { - "name": "Mine", - "accounts": [ - { - "name": "oreProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore program" - ] - }, - { - "name": "signer", - "isMut": false, - "isSigner": true, - "docs": [ - "Signer" - ] - }, - { - "name": "bus", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account" - ] - }, - { - "name": "proof", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore miner proof account" - ] - }, - { - "name": "treasury", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore treasury account" - ] - }, - { - "name": "slotHashes", - "isMut": false, - "isSigner": false, - "docs": [ - "Solana slot hashes sysvar" - ] - } - ], - "args": [], - "discriminant": { - "type": "u8", - "value": 2 - } - }, - { - "name": "Claim", - "accounts": [ - { - "name": "oreProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore program" - ] - }, - { - "name": "signer", - "isMut": false, - "isSigner": true, - "docs": [ - "Signer" - ] - }, - { - "name": "beneficiary", - "isMut": true, - "isSigner": false, - "docs": [ - "Beneficiary token account" - ] - }, - { - "name": "mint", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore token mint account" - ] - }, - { - "name": "proof", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore miner proof account" - ] - }, - { - "name": "treasury", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore treasury account" - ] - }, - { - "name": "treasuryTokens", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore treasury token account" - ] - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "SPL token program" - ] - } - ], - "args": [], - "discriminant": { - "type": "u8", - "value": 3 - } - }, - { - "name": "Initialize", - "accounts": [ - { - "name": "oreProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore program" - ] - }, - { - "name": "admin", - "isMut": false, - "isSigner": true, - "docs": [ - "Admin signer" - ] - }, - { - "name": "bus0", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 0" - ] - }, - { - "name": "bus1", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 1" - ] - }, - { - "name": "bus2", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 2" - ] - }, - { - "name": "bus3", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 3" - ] - }, - { - "name": "bus4", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 4" - ] - }, - { - "name": "bus5", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 5" - ] - }, - { - "name": "bus6", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 6" - ] - }, - { - "name": "bus7", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 7" - ] - }, - { - "name": "mint", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore token mint account" - ] - }, - { - "name": "treasury", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore treasury account" - ] - }, - { - "name": "treasuryTokens", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore treasury token account" - ] - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Solana system program" - ] - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "SPL token program" - ] - }, - { - "name": "associatedTokenProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "SPL associated token program" - ] - }, - { - "name": "rent", - "isMut": false, - "isSigner": false, - "docs": [ - "Solana rent sysvar" - ] - } - ], - "args": [], - "discriminant": { - "type": "u8", - "value": 100 - } - }, - { - "name": "UpdateAdmin", - "accounts": [ - { - "name": "oreProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore program" - ] - }, - { - "name": "treasury", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore treasury account" - ] - } - ], - "args": [], - "discriminant": { - "type": "u8", - "value": 102 - } - }, - { - "name": "UpdateDifficulty", - "accounts": [ - { - "name": "oreProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore program" - ] - }, - { - "name": "treasury", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore treasury account" - ] - } - ], - "args": [], - "discriminant": { - "type": "u8", - "value": 103 - } - } - ], - "accounts": [ - { - "name": "Bus", - "type": { - "kind": "struct", - "fields": [ - { - "name": "id", - "type": "u64" - }, - { - "name": "rewards", - "type": "u64" - } - ] - } - }, - { - "name": "Proof", - "type": { - "kind": "struct", - "fields": [ - { - "name": "authority", - "type": "publicKey" - }, - { - "name": "claimableRewards", - "type": "u64" - }, - { - "name": "hash", - "type": { - "defined": "Hash" - } - }, - { - "name": "totalHashes", - "type": "u64" - }, - { - "name": "totalRewards", - "type": "u64" - } - ] - } - }, - { - "name": "Treasury", - "type": { - "kind": "struct", - "fields": [ - { - "name": "admin", - "type": "publicKey" - }, - { - "name": "bump", - "type": "u64" - }, - { - "name": "difficulty", - "type": { - "defined": "Hash" - } - }, - { - "name": "epochStartAt", - "type": "i64" - }, - { - "name": "rewardRate", - "type": "u64" - }, - { - "name": "totalClaimedRewards", - "type": "u64" - } - ] - } - } - ], - "errors": [ - { - "code": 0, - "name": "EpochActive", - "msg": "The epoch is still active and cannot be reset" - }, - { - "code": 1, - "name": "EpochExpired", - "msg": "The epoch has expired and needs reset" - }, - { - "code": 2, - "name": "InvalidHash", - "msg": "The provided hash was invalid" - }, - { - "code": 3, - "name": "InsufficientHashDifficulty", - "msg": "The provided hash does not satisfy the difficulty requirement" - }, - { - "code": 4, - "name": "InsufficientBusRewards", - "msg": "The bus has insufficient rewards to mine at this time" - }, - { - "code": 5, - "name": "InvalidClaimAmount", - "msg": "The claim amount cannot be larger than the claimable rewards" - } - ], - "metadata": { - "origin": "shank", - "address": "ore2mSzJwAZhxLyCLbNEnFvYq9U8jvCMvUBrVvbmqDF" - } -} \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 833a265..fa4b8db 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.75.0" +channel = "1.76.0" components = [ "rustfmt", "rust-analyzer" ] targets = [ "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-apple-darwin"] profile = "minimal" diff --git a/src/consts.rs b/src/consts.rs deleted file mode 100644 index 095c39e..0000000 --- a/src/consts.rs +++ /dev/null @@ -1,96 +0,0 @@ -use solana_program::{keccak::Hash, pubkey, pubkey::Pubkey}; - -/// The unix timestamp after which mining can begin. -pub const START_AT: i64 = 1712070600; - -/// The reward rate to intialize the program with. -pub const INITIAL_REWARD_RATE: u64 = 10u64.pow(3u32); - -/// The mining difficulty to initialize the program with. -pub const INITIAL_DIFFICULTY: Hash = Hash::new_from_array([ - 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -]); - -/// The decimal precision of the ORE token. -/// Using SI prefixes, the smallest indivisible unit of ORE is a nanoORE. -/// 1 nanoORE = 0.000000001 ORE = one billionth of an ORE -pub const TOKEN_DECIMALS: u8 = 9; - -/// One ORE token, denominated in units of nanoORE. -pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32); - -/// The duration of an epoch, in units of seconds. -pub const EPOCH_DURATION: i64 = 60; - -/// The target quantity of ORE to be mined per epoch, in units of nanoORE. -/// Inflation rate ≈ 1 ORE / epoch (min 0, max 2) -pub const TARGET_EPOCH_REWARDS: u64 = ONE_ORE; - -/// The maximum quantity of ORE that can be mined per epoch, in units of nanoORE. -pub const MAX_EPOCH_REWARDS: u64 = ONE_ORE.saturating_mul(2); - -/// The quantity of ORE each bus is allowed to issue per epoch. -pub const BUS_EPOCH_REWARDS: u64 = MAX_EPOCH_REWARDS.saturating_div(BUS_COUNT as u64); - -/// The number of bus accounts, for parallelizing mine operations. -pub const BUS_COUNT: usize = 8; - -/// The smoothing factor for reward rate changes. The reward rate cannot change by more or less -/// than a factor of this constant from one epoch to the next. -pub const SMOOTHING_FACTOR: u64 = 2; - -// Assert MAX_EPOCH_REWARDS is evenly divisible by BUS_COUNT. -static_assertions::const_assert!( - (MAX_EPOCH_REWARDS / BUS_COUNT as u64) * BUS_COUNT as u64 == MAX_EPOCH_REWARDS -); - -/// The seed of the bus account PDA. -pub const BUS: &[u8] = b"bus"; - -/// The seed of the metadata account PDA. -pub const METADATA: &[u8] = b"metadata"; - -/// The seed of the mint account PDA. -pub const MINT: &[u8] = b"mint"; - -/// The seed of proof account PDAs. -pub const PROOF: &[u8] = b"proof"; - -/// The seed of the treasury account PDA. -pub const TREASURY: &[u8] = b"treasury"; - -/// The name for token metadata. -pub const METADATA_NAME: &str = "Ore"; - -/// The ticker symbol for token metadata. -pub const METADATA_SYMBOL: &str = "ORE"; - -/// The uri for token metdata. -pub const METADATA_URI: &str = "https://ore.supply/metadata.json"; - -/// Noise for deriving the mint PDA. -pub const MINT_NOISE: [u8; 16] = [ - 166, 199, 85, 221, 225, 119, 21, 185, 160, 82, 242, 237, 194, 84, 250, 252, -]; - -/// The addresses of the bus accounts. -pub const BUS_ADDRESSES: [Pubkey; BUS_COUNT] = [ - pubkey!("9ShaCzHhQNvH8PLfGyrJbB8MeKHrDnuPMLnUDLJ2yMvz"), - pubkey!("4Cq8685h9GwsaD5ppPsrtfcsk3fum8f9UP4SPpKSbj2B"), - pubkey!("8L1vdGdvU3cPj9tsjJrKVUoBeXYvAzJYhExjTYHZT7h7"), - pubkey!("JBdVURCrUiHp4kr7srYtXbB7B4CwurUt1Bfxrxw6EoRY"), - pubkey!("DkmVBWJ4CLKb3pPHoSwYC2wRZXKKXLD2Ued5cGNpkWmr"), - pubkey!("9uLpj2ZCMqN6Yo1vV6yTkP6dDiTTXmeM5K3915q5CHyh"), - pubkey!("EpcfjBs8eQ4unSMdowxyTE8K3vVJ3XUnEr5BEWvSX7RB"), - pubkey!("Ay5N9vKS2Tyo2M9u9TFt59N1XbxdW93C7UrFZW3h8sMC"), -]; - -/// The address of the mint metadata account. -pub const METADATA_ADDRESS: Pubkey = pubkey!("2nXZSxfjELuRatcoY64yHdFLZFi3mtesxobHmsoU3Dag"); - -/// The address of the mint account. -pub const MINT_ADDRESS: Pubkey = pubkey!("oreoN2tQbHXVaZsr3pf66A48miqcBXCDJozganhEJgz"); - -/// The address of the treasury account. -pub const TREASURY_ADDRESS: Pubkey = pubkey!("FTap9fv2GPpWGqrLj3o4c9nHH7p36ih7NbSWHnrkQYqa"); diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 597331b..0000000 --- a/src/error.rs +++ /dev/null @@ -1,28 +0,0 @@ -use num_enum::IntoPrimitive; -use solana_program::program_error::ProgramError; -use thiserror::Error; - -#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, IntoPrimitive)] -#[repr(u32)] -pub enum OreError { - #[error("The starting time has not passed yet")] - NotStarted = 0, - #[error("The epoch has ended and needs reset")] - NeedsReset = 1, - #[error("The epoch is active and cannot be reset at this time")] - ResetTooEarly = 2, - #[error("The provided hash was invalid")] - HashInvalid = 3, - #[error("The provided hash does not satisfy the difficulty requirement")] - DifficultyNotSatisfied = 4, - #[error("The bus does not have enough rewards to issue at this time")] - BusRewardsInsufficient = 5, - #[error("The claim amount cannot be greater than the claimable rewards")] - ClaimTooLarge = 6, -} - -impl From for ProgramError { - fn from(e: OreError) -> Self { - ProgramError::Custom(e as u32) - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 1cc357d..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub mod consts; -pub mod error; -pub mod instruction; -mod loaders; -mod processor; -pub mod state; -pub mod utils; - -pub use consts::*; -use instruction::*; -use processor::*; -use solana_program::{ - self, account_info::AccountInfo, declare_id, entrypoint::ProgramResult, - program_error::ProgramError, pubkey::Pubkey, -}; - -declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); - -#[cfg(not(feature = "no-entrypoint"))] -solana_program::entrypoint!(process_instruction); - -pub fn process_instruction( - program_id: &Pubkey, - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - if program_id.ne(&crate::id()) { - return Err(ProgramError::IncorrectProgramId); - } - - let (tag, data) = data - .split_first() - .ok_or(ProgramError::InvalidInstructionData)?; - - match OreInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))? { - OreInstruction::Reset => process_reset(program_id, accounts, data)?, - OreInstruction::Register => process_register(program_id, accounts, data)?, - OreInstruction::Mine => process_mine(program_id, accounts, data)?, - OreInstruction::Claim => process_claim(program_id, accounts, data)?, - OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, - OreInstruction::UpdateAdmin => process_update_admin(program_id, accounts, data)?, - OreInstruction::UpdateDifficulty => process_update_difficulty(program_id, accounts, data)?, - } - - Ok(()) -} diff --git a/src/loaders.rs b/src/loaders.rs deleted file mode 100644 index 902b996..0000000 --- a/src/loaders.rs +++ /dev/null @@ -1,1271 +0,0 @@ -use solana_program::{ - account_info::AccountInfo, program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, - system_program, sysvar, -}; -use spl_token::state::Mint; - -use crate::{ - state::{Bus, Proof, Treasury}, - utils::AccountDeserialize, - BUS_ADDRESSES, BUS_COUNT, MINT_ADDRESS, TREASURY_ADDRESS, -}; - -/// Errors if: -/// - Account is not a signer. -pub fn load_signer<'a, 'info>(info: &'a AccountInfo<'info>) -> Result<(), ProgramError> { - if !info.is_signer { - return Err(ProgramError::MissingRequiredSignature); - } - - Ok(()) -} - -/// Errors if: -/// - Owner is not Ore program. -/// - Address does not match the expected bus address. -/// - Data is empty. -/// - Data cannot deserialize into a bus account. -/// - Bus ID does not match the expected ID. -/// - Expected to be writable, but is not. -pub fn load_bus<'a, 'info>( - info: &'a AccountInfo<'info>, - id: u64, - is_writable: bool, -) -> Result<(), ProgramError> { - if info.owner.ne(&crate::id()) { - return Err(ProgramError::InvalidAccountOwner); - } - - if info.key.ne(&BUS_ADDRESSES[id as usize]) { - return Err(ProgramError::InvalidSeeds); - } - - if info.data_is_empty() { - return Err(ProgramError::UninitializedAccount); - } - - let bus_data = info.data.borrow(); - let bus = Bus::try_from_bytes(&bus_data)?; - - if bus.id.ne(&id) { - return Err(ProgramError::InvalidAccountData); - } - - if is_writable && !info.is_writable { - return Err(ProgramError::InvalidAccountData); - } - - Ok(()) -} - -/// Errors if: -/// - Owner is not Ore program. -/// - Data is empty. -/// - Data cannot deserialize into a bus account. -/// - Bus ID is not in the expected range. -/// - Address is not in set of valid bus address. -/// - Expected to be writable, but is not. -pub fn load_any_bus<'a, 'info>( - info: &'a AccountInfo<'info>, - is_writable: bool, -) -> Result<(), ProgramError> { - if info.owner.ne(&crate::id()) { - return Err(ProgramError::InvalidAccountOwner); - } - - if info.data_is_empty() { - return Err(ProgramError::UninitializedAccount); - } - - let bus_data = info.data.borrow(); - let bus = Bus::try_from_bytes(&bus_data)?; - - if bus.id.ge(&(BUS_COUNT as u64)) { - return Err(ProgramError::InvalidAccountData); - } - - if info.key.ne(&BUS_ADDRESSES[bus.id as usize]) { - return Err(ProgramError::InvalidSeeds); - } - - if is_writable && !info.is_writable { - return Err(ProgramError::InvalidAccountData); - } - - Ok(()) -} - -/// Errors if: -/// - Owner is not Ore program. -/// - Data is empty. -/// - Data cannot deserialize into a proof account. -/// - Proof authority does not match the expected address. -/// - Expected to be writable, but is not. -pub fn load_proof<'a, 'info>( - info: &'a AccountInfo<'info>, - authority: &Pubkey, - is_writable: bool, -) -> Result<(), ProgramError> { - if info.owner.ne(&crate::id()) { - return Err(ProgramError::InvalidAccountOwner); - } - - if info.data_is_empty() { - return Err(ProgramError::UninitializedAccount); - } - - let proof_data = info.data.borrow(); - let proof = Proof::try_from_bytes(&proof_data)?; - - if proof.authority.ne(&authority) { - return Err(ProgramError::InvalidAccountData); - } - - if is_writable && !info.is_writable { - return Err(ProgramError::InvalidAccountData); - } - - Ok(()) -} - -/// Errors if: -/// - Owner is not Ore program. -/// - Address does not match the expected address. -/// - Data is empty. -/// - Data cannot deserialize into a treasury account. -/// - Expected to be writable, but is not. -pub fn load_treasury<'a, 'info>( - info: &'a AccountInfo<'info>, - is_writable: bool, -) -> Result<(), ProgramError> { - if info.owner.ne(&crate::id()) { - return Err(ProgramError::InvalidAccountOwner); - } - - if info.key.ne(&TREASURY_ADDRESS) { - return Err(ProgramError::InvalidSeeds); - } - - if info.data_is_empty() { - return Err(ProgramError::UninitializedAccount); - } - - let treasury_data = info.data.borrow(); - let _ = Treasury::try_from_bytes(&treasury_data)?; - - if is_writable && !info.is_writable { - return Err(ProgramError::InvalidAccountData); - } - - Ok(()) -} - -/// Errors if: -/// - Owner is not SPL token program. -/// - Address does not match the expected mint address. -/// - Data is empty. -/// - Data cannot deserialize into a mint account. -/// - Expected to be writable, but is not. -pub fn load_mint<'a, 'info>( - info: &'a AccountInfo<'info>, - is_writable: bool, -) -> Result<(), ProgramError> { - if info.owner.ne(&spl_token::id()) { - return Err(ProgramError::InvalidAccountOwner); - } - - if info.key.ne(&MINT_ADDRESS) { - return Err(ProgramError::InvalidSeeds); - } - - if info.data_is_empty() { - return Err(ProgramError::UninitializedAccount); - } - - let mint_data = info.data.borrow(); - if Mint::unpack_unchecked(&mint_data).is_err() { - return Err(ProgramError::InvalidAccountData); - } - - if is_writable && !info.is_writable { - return Err(ProgramError::InvalidAccountData); - } - - Ok(()) -} - -/// Errors if: -/// - Owner is not SPL token program. -/// - Data is empty. -/// - Data cannot deserialize into a token account. -/// - Token account owner does not match the expected owner address. -/// - Token account mint does not match the expected mint address. -/// - Expected to be writable, but is not. -pub fn load_token_account<'a, 'info>( - info: &'a AccountInfo<'info>, - owner: Option<&Pubkey>, - mint: &Pubkey, - is_writable: bool, -) -> Result<(), ProgramError> { - if info.owner.ne(&spl_token::id()) { - return Err(ProgramError::InvalidAccountOwner); - } - - if info.data_is_empty() { - return Err(ProgramError::UninitializedAccount); - } - - let account_data = info.data.borrow(); - let account = spl_token::state::Account::unpack_unchecked(&account_data) - .or(Err(ProgramError::InvalidAccountData))?; - - if account.mint.ne(&mint) { - return Err(ProgramError::InvalidAccountData); - } - - if let Some(owner) = owner { - if account.owner.ne(owner) { - return Err(ProgramError::InvalidAccountData); - } - } - - if is_writable && !info.is_writable { - return Err(ProgramError::InvalidAccountData); - } - - Ok(()) -} - -/// Errors if: -/// - Address does not match PDA derived from provided seeds. -/// - Cannot load as an uninitialized account. -pub fn load_uninitialized_pda<'a, 'info>( - info: &'a AccountInfo<'info>, - seeds: &[&[u8]], - bump: u8, - program_id: &Pubkey, -) -> Result<(), ProgramError> { - let pda = Pubkey::find_program_address(seeds, program_id); - - if info.key.ne(&pda.0) { - return Err(ProgramError::InvalidSeeds); - } - - if bump.ne(&pda.1) { - return Err(ProgramError::InvalidSeeds); - } - - load_uninitialized_account(info) -} - -/// Errors if: -/// - Owner is not the system program. -/// - Data is not empty. -/// - Account is not writable. -pub fn load_uninitialized_account<'a, 'info>( - info: &'a AccountInfo<'info>, -) -> Result<(), ProgramError> { - if info.owner.ne(&system_program::id()) { - return Err(ProgramError::InvalidAccountOwner); - } - - if !info.data_is_empty() { - return Err(ProgramError::AccountAlreadyInitialized); - } - - if !info.is_writable { - return Err(ProgramError::InvalidAccountData); - } - - Ok(()) -} - -/// Errors if: -/// - Owner is not the sysvar address. -/// - Account cannot load with the expected address. -pub fn load_sysvar<'a, 'info>( - info: &'a AccountInfo<'info>, - key: Pubkey, -) -> Result<(), ProgramError> { - if info.owner.ne(&sysvar::id()) { - return Err(ProgramError::InvalidAccountOwner); - } - - load_account(info, key, false) -} - -/// Errors if: -/// - Address does not match the expected value. -/// - Expected to be writable, but is not. -pub fn load_account<'a, 'info>( - info: &'a AccountInfo<'info>, - key: Pubkey, - is_writable: bool, -) -> Result<(), ProgramError> { - if info.key.ne(&key) { - return Err(ProgramError::InvalidAccountData); - } - - if is_writable && !info.is_writable { - return Err(ProgramError::InvalidAccountData); - } - - Ok(()) -} - -/// Errors if: -/// - Address does not match the expected value. -/// - Account is not executable. -pub fn load_program<'a, 'info>( - info: &'a AccountInfo<'info>, - key: Pubkey, -) -> Result<(), ProgramError> { - if info.key.ne(&key) { - return Err(ProgramError::IncorrectProgramId); - } - - if !info.executable { - return Err(ProgramError::InvalidAccountData); - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use solana_program::{ - account_info::AccountInfo, keccak::Hash as KeccakHash, program_option::COption, - program_pack::Pack, pubkey::Pubkey, system_program, - }; - use spl_token::state::{AccountState, Mint}; - - use crate::{ - loaders::{ - load_account, load_any_bus, load_bus, load_mint, load_proof, load_signer, load_sysvar, - load_token_account, load_treasury, load_uninitialized_account, load_uninitialized_pda, - }, - state::{Bus, Proof, Treasury}, - utils::Discriminator, - BUS, BUS_ADDRESSES, BUS_COUNT, MINT_ADDRESS, PROOF, TOKEN_DECIMALS, TREASURY, - TREASURY_ADDRESS, - }; - - use super::load_program; - - #[test] - pub fn test_signer_not_signer() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_signer(&info).is_err()); - } - - #[test] - pub fn test_load_bus_bad_account_owner() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_bus(&info, 0, true).is_err()); - } - - #[test] - pub fn test_load_bus_bad_key() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_bus(&info, 0, true).is_err()); - } - - #[test] - pub fn test_load_bus_empty_data() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_bus(&info, 0, true).is_err()); - } - - #[test] - pub fn test_load_bus_bad_data() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = [ - &(Treasury::discriminator() as u64).to_le_bytes(), // Bus discriminator - Bus { id: 0, rewards: 0 }.to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_bus(&info, 0, true).is_err()); - } - - #[test] - pub fn test_load_bus_bad_id() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = [ - &(Bus::discriminator() as u64).to_le_bytes(), // Bus discriminator - Bus { id: 1, rewards: 0 }.to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_bus(&info, 0, true).is_err()); - } - - #[test] - pub fn test_load_bus_not_writeable() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = [ - &(Bus::discriminator() as u64).to_le_bytes(), - Bus { id: 0, rewards: 0 }.to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_bus(&info, 0, true).is_err()); - } - - #[test] - pub fn test_load_any_bus_bad_account_owner() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_any_bus(&info, true).is_err()); - } - - #[test] - pub fn test_load_any_bus_bad_key() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_any_bus(&info, true).is_err()); - } - - #[test] - pub fn test_load_any_bus_empty_data() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_any_bus(&info, true).is_err()); - } - - #[test] - pub fn test_load_any_bus_bad_data() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = [ - &(Treasury::discriminator() as u64).to_le_bytes(), // Treasury discriminator - Bus { id: 0, rewards: 0 }.to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_any_bus(&info, true).is_err()); - } - - #[test] - pub fn test_load_any_bus_bad_id() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = [ - &(Bus::discriminator() as u64).to_le_bytes(), - Bus { - id: (BUS_COUNT + 1) as u64, - rewards: 0, - } - .to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_any_bus(&info, true).is_err()); - } - - #[test] - pub fn test_load_any_bus_mismatch_id() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = [ - &(Bus::discriminator() as u64).to_le_bytes(), - Bus { - id: 1 as u64, - rewards: 0, - } - .to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_any_bus(&info, true).is_err()); - } - - #[test] - pub fn test_load_any_bus_not_writeable() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = [ - &(Bus::discriminator() as u64).to_le_bytes(), - Bus { id: 0, rewards: 0 }.to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_any_bus(&info, true).is_err()); - } - - #[test] - pub fn test_load_proof_bad_account_owner() { - let authority = Pubkey::new_unique(); - let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &pda.0, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_proof(&info, &authority, true).is_err()); - } - - #[test] - pub fn test_load_proof_bad_key() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_proof(&info, &Pubkey::new_unique(), true).is_err()); - } - - #[test] - pub fn test_load_proof_empty_data() { - let authority = Pubkey::new_unique(); - let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &pda.0, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_proof(&info, &authority, true).is_err()); - } - - #[test] - pub fn test_load_proof_bad_data() { - let authority = Pubkey::new_unique(); - let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = [ - &(Bus::discriminator() as u64).to_le_bytes(), // Bus discriminator - Proof { - authority, - claimable_rewards: 0, - hash: KeccakHash::new_from_array([u8::MAX; 32]).into(), - total_hashes: 0, - total_rewards: 0, - } - .to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &pda.0, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_proof(&info, &authority, true).is_err()); - } - - #[test] - pub fn test_load_proof_not_writeable() { - let authority = Pubkey::new_unique(); - let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = [ - &(Proof::discriminator() as u64).to_le_bytes(), - Proof { - authority, - claimable_rewards: 0, - hash: KeccakHash::new_from_array([u8::MAX; 32]).into(), - total_hashes: 0, - total_rewards: 0, - } - .to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &pda.0, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_proof(&info, &authority, true).is_err()); - } - - #[test] - pub fn test_load_treasury_bad_account_owner() { - let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &pda.0, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_treasury(&info, true).is_err()); - } - - #[test] - pub fn test_load_treasury_bad_key() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_treasury(&info, true).is_err()); - } - - #[test] - pub fn test_load_treasury_empty_data() { - let key = TREASURY_ADDRESS; - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_treasury(&info, true).is_err()); - } - - #[test] - pub fn test_load_treasury_bad_data() { - let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = [ - &(Bus::discriminator() as u64).to_le_bytes(), // Bus discriminator - Treasury { - bump: pda.1 as u64, - admin: Pubkey::new_unique(), - difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), - last_reset_at: 0, - reward_rate: 100, - total_claimed_rewards: 0, - } - .to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &pda.0, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_treasury(&info, true).is_err()); - } - - #[test] - pub fn test_load_treasury_not_writeable() { - let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = [ - &(Treasury::discriminator() as u64).to_le_bytes(), - Treasury { - bump: pda.1 as u64, - admin: Pubkey::new_unique(), - difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), - last_reset_at: 0, - reward_rate: 100, - total_claimed_rewards: 0, - } - .to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &pda.0, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_treasury(&info, true).is_err()); - } - - #[test] - pub fn test_load_mint_bad_account_owner() { - let key = MINT_ADDRESS; - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); - } - - #[test] - pub fn test_load_mint_bad_key() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_mint(&info, true).is_err()); - } - - #[test] - pub fn test_load_mint_empty_data() { - let key = MINT_ADDRESS; - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_mint(&info, true).is_err()); - } - - #[test] - pub fn test_load_mint_bad_data() { - let key = MINT_ADDRESS; - let mut lamports = 1_000_000_000; - let mut data = [1]; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_mint(&info, true).is_err()); - } - - #[test] - pub fn test_load_mint_not_writeable() { - let mut data: [u8; Mint::LEN] = [0; Mint::LEN]; - Mint { - mint_authority: COption::Some(TREASURY_ADDRESS), - supply: 0, - decimals: TOKEN_DECIMALS, - is_initialized: true, - freeze_authority: COption::None, - } - .pack_into_slice(&mut data); - let key = MINT_ADDRESS; - let mut lamports = 1_000_000_000; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_mint(&info, true).is_err()); - } - - #[test] - pub fn test_load_token_account_bad_account_owner() { - let mut data: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; - spl_token::state::Account { - mint: MINT_ADDRESS, - owner: TREASURY_ADDRESS, - amount: 2_000_000_000, - delegate: COption::None, - state: AccountState::Initialized, - is_native: COption::None, - delegated_amount: 0, - close_authority: COption::None, - } - .pack_into_slice(&mut data); - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); - } - - #[test] - pub fn test_load_token_account_empty_data() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); - } - - #[test] - pub fn test_load_token_account_bad_data() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = [1]; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); - } - - #[test] - pub fn test_load_token_account_bad_owner_mint() { - let mut data: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; - spl_token::state::Account { - mint: MINT_ADDRESS, - owner: TREASURY_ADDRESS, - amount: 2_000_000_000, - delegate: COption::None, - state: AccountState::Initialized, - is_native: COption::None, - delegated_amount: 0, - close_authority: COption::None, - } - .pack_into_slice(&mut data); - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_token_account(&info, Some(&key), &MINT_ADDRESS, false).is_err()); - assert!(load_token_account(&info, None, &Pubkey::new_unique(), false).is_err()); - assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); - } - - #[test] - pub fn test_load_uninitialized_pda_bad_key_bump() { - let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &pda.0, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_uninitialized_pda(&info, &[BUS], pda.1, &crate::id()).is_err()); - assert!(load_uninitialized_pda(&info, &[TREASURY], 0, &crate::id()).is_err()); - } - - #[test] - pub fn test_load_uninitialized_account_bad_owner() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_uninitialized_account(&info).is_err()); - } - - #[test] - pub fn test_load_uninitialized_account_data_not_empty() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = [0]; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_uninitialized_account(&info).is_err()); - } - - #[test] - pub fn test_load_uninitialized_account_not_writeable() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_uninitialized_account(&info).is_err()); - } - - #[test] - pub fn test_load_sysvar_bad_owner() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_sysvar(&info, key).is_err()); - } - - #[test] - pub fn test_load_account_bad_key() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_account(&info, Pubkey::new_unique(), false).is_err()); - } - - #[test] - pub fn test_load_account_not_writeable() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_account(&info, key, true).is_err()); - } - - #[test] - pub fn test_load_program_bad_key() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - true, - 0, - ); - assert!(load_program(&info, Pubkey::new_unique()).is_err()); - } - - #[test] - pub fn test_load_program_not_executable() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_program(&info, key).is_err()); - } -} diff --git a/src/processor/mine.rs b/src/processor/mine.rs deleted file mode 100644 index f318e19..0000000 --- a/src/processor/mine.rs +++ /dev/null @@ -1,183 +0,0 @@ -use std::mem::size_of; - -use solana_program::{ - account_info::AccountInfo, - clock::Clock, - entrypoint::ProgramResult, - keccak::{hashv, Hash as KeccakHash, HASH_BYTES}, - program::set_return_data, - program_error::ProgramError, - program_memory::sol_memcmp, - pubkey::Pubkey, - slot_hashes::SlotHash, - sysvar::{self, Sysvar}, -}; - -use crate::{ - error::OreError, - instruction::MineArgs, - loaders::*, - state::{Bus, Proof, Treasury}, - utils::AccountDeserialize, - EPOCH_DURATION, START_AT, -}; - -/// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: -/// 1. Verify the provided hash is valid. -/// 2. Increment the user's claimable rewards counter. -/// 3. Generate a new challenge for the miner. -/// 4. Update the miner's lifetime stats. -/// -/// Safety requirements: -/// - Mine is a permissionless instruction and can be called by any signer. -/// - Can only succeed if START_AT has passed. -/// - Can only succeed if the last reset was less than 60 seconds ago. -/// - Can only succeed if the provided SHA3 hash and nonce are valid and satisfy the difficulty. -/// - The the provided proof account must be associated with the signer. -/// - The provided bus, treasury, and slot hash sysvar must be valid. -pub fn process_mine<'a, 'info>( - _program_id: &Pubkey, - accounts: &'a [AccountInfo<'info>], - data: &[u8], -) -> ProgramResult { - // Parse args - let args = MineArgs::try_from_bytes(data)?; - - // Load accounts - let [signer, bus_info, proof_info, treasury_info, slot_hashes_info] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - load_signer(signer)?; - load_any_bus(bus_info, true)?; - load_proof(proof_info, signer.key, true)?; - load_treasury(treasury_info, false)?; - load_sysvar(slot_hashes_info, sysvar::slot_hashes::id())?; - - // Validate mining has starting - let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; - if clock.unix_timestamp.lt(&START_AT) { - return Err(OreError::NotStarted.into()); - } - - // Validate epoch is active - let treasury_data = treasury_info.data.borrow(); - let treasury = Treasury::try_from_bytes(&treasury_data)?; - let threshold = treasury.last_reset_at.saturating_add(EPOCH_DURATION); - if clock.unix_timestamp.ge(&threshold) { - return Err(OreError::NeedsReset.into()); - } - - // Validate provided hash - let mut proof_data = proof_info.data.borrow_mut(); - let proof = Proof::try_from_bytes_mut(&mut proof_data)?; - validate_hash( - args.hash.into(), - proof.hash.into(), - *signer.key, - u64::from_le_bytes(args.nonce), - treasury.difficulty.into(), - )?; - - // Update claimable rewards - let mut bus_data = bus_info.data.borrow_mut(); - let bus = Bus::try_from_bytes_mut(&mut bus_data)?; - bus.rewards = bus - .rewards - .checked_sub(treasury.reward_rate) - .ok_or(OreError::BusRewardsInsufficient)?; - proof.claimable_rewards = proof.claimable_rewards.saturating_add(treasury.reward_rate); - - // Hash recent slot hash into the next challenge to prevent pre-mining attacks - proof.hash = hashv(&[ - KeccakHash::from(args.hash).as_ref(), - &slot_hashes_info.data.borrow()[0..size_of::()], - ]) - .into(); - - // Update lifetime stats - proof.total_hashes = proof.total_hashes.saturating_add(1); - proof.total_rewards = proof.total_rewards.saturating_add(treasury.reward_rate); - - // Log the mined rewards - set_return_data(treasury.reward_rate.to_le_bytes().as_slice()); - - Ok(()) -} - -/// Validates the provided hash, ensursing it is equal to SHA3(current_hash, singer, nonce). -/// Fails if the provided hash is valid but does not satisfy the required difficulty. -pub(crate) fn validate_hash( - hash: KeccakHash, - current_hash: KeccakHash, - signer: Pubkey, - nonce: u64, - difficulty: KeccakHash, -) -> Result<(), ProgramError> { - // Validate hash correctness - let hash_ = hashv(&[ - current_hash.as_ref(), - signer.as_ref(), - nonce.to_le_bytes().as_slice(), - ]); - if sol_memcmp(hash.as_ref(), hash_.as_ref(), HASH_BYTES) != 0 { - return Err(OreError::HashInvalid.into()); - } - - // Validate hash difficulty - if hash.gt(&difficulty) { - return Err(OreError::DifficultyNotSatisfied.into()); - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use solana_program::{ - keccak::{hashv, Hash, HASH_BYTES}, - pubkey::Pubkey, - }; - - use crate::validate_hash; - - #[test] - fn test_validate_hash_pass() { - let h1 = Hash::new_from_array([1; HASH_BYTES]); - let signer = Pubkey::new_unique(); - let nonce = 10u64; - let difficulty = Hash::new_from_array([255; HASH_BYTES]); - let h2 = hashv(&[ - h1.to_bytes().as_slice(), - signer.to_bytes().as_slice(), - nonce.to_le_bytes().as_slice(), - ]); - let res = validate_hash(h2, h1, signer, nonce, difficulty); - assert!(res.is_ok()); - } - - #[test] - fn test_validate_hash_fail() { - let h1 = Hash::new_from_array([1; HASH_BYTES]); - let signer = Pubkey::new_unique(); - let nonce = 10u64; - let difficulty = Hash::new_from_array([255; HASH_BYTES]); - let h2 = Hash::new_from_array([2; HASH_BYTES]); - let res = validate_hash(h2, h1, signer, nonce, difficulty); - assert!(res.is_err()); - } - - #[test] - fn test_validate_hash_fail_difficulty() { - let h1 = Hash::new_from_array([1; HASH_BYTES]); - let signer = Pubkey::new_unique(); - let nonce = 10u64; - let difficulty = Hash::new_from_array([0; HASH_BYTES]); - let h2 = hashv(&[ - h1.to_bytes().as_slice(), - signer.to_bytes().as_slice(), - nonce.to_le_bytes().as_slice(), - ]); - let res = validate_hash(h2, h1, signer, nonce, difficulty); - assert!(res.is_err()); - } -} diff --git a/src/processor/mod.rs b/src/processor/mod.rs deleted file mode 100644 index 532584e..0000000 --- a/src/processor/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod claim; -mod initialize; -mod mine; -mod register; -mod reset; -mod update_admin; -mod update_difficulty; - -pub use claim::*; -pub use initialize::*; -pub use mine::*; -pub use register::*; -pub use reset::*; -pub use update_admin::*; -pub use update_difficulty::*; diff --git a/src/processor/update_admin.rs b/src/processor/update_admin.rs deleted file mode 100644 index a006b55..0000000 --- a/src/processor/update_admin.rs +++ /dev/null @@ -1,54 +0,0 @@ -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, -}; - -use crate::{instruction::UpdateAdminArgs, loaders::*, state::Treasury, utils::AccountDeserialize}; - -/// UpdateAdmin updates the program's admin account. Its responsibilities include: -/// 1. Update the treasury admin address. -/// -/// Safety requirements: -/// - Can only succeed if the signer is the program admin. -/// - Can only succeed if the provided treasury is valid. -/// -/// Discussion: -/// - The admin authority has one lever of power: the ability to adjust the global -/// mining difficulty. If the difficulty is too easy, miners will find hashes very quickly -/// and the bottleneck for mining will shift from local compute to Solana bandwidth. In essence, -/// if the Ore token has value and difficulty is low, mining becomes an incentivized stress -/// test for the Solana network. -/// - At the same time, if difficulty is too hard, miners will have to wait a very long period -/// of time between finding valid hashes. This will bias rewards to well-resourced miners -/// with large compute operations. Keeping a low difficulty ensures casual miners can -/// consistently earn rewards and undercuts some of the advantage of larger players. -/// - Ultimately admin authority should be delegated to a governance mechanism – either -/// democratic or futarchic – to ensure difficulty is kept at a value that represents the -/// values and interests of the ecosystem. -pub fn process_update_admin<'a, 'info>( - _program_id: &Pubkey, - accounts: &'a [AccountInfo<'info>], - data: &[u8], -) -> ProgramResult { - // Parse args - let args = UpdateAdminArgs::try_from_bytes(data)?; - - // Load accounts - let [signer, treasury_info] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - load_signer(signer)?; - load_treasury(treasury_info, true)?; - - // Validate signer is admin - let mut treasury_data = treasury_info.data.borrow_mut(); - let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; - if treasury.admin.ne(&signer.key) { - return Err(ProgramError::MissingRequiredSignature); - } - - // Update admin - treasury.admin = args.new_admin; - - Ok(()) -} diff --git a/src/processor/update_difficulty.rs b/src/processor/update_difficulty.rs deleted file mode 100644 index 8b77d53..0000000 --- a/src/processor/update_difficulty.rs +++ /dev/null @@ -1,55 +0,0 @@ -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, -}; - -use crate::{ - instruction::UpdateDifficultyArgs, loaders::*, state::Treasury, utils::AccountDeserialize, -}; - -/// UpdateDifficulty updates the program's global difficulty value. Its responsibilities include: -/// 1. Update the mining difficulty. -/// -/// Safety requirements: -/// - Can only succeed if the signer is the program admin. -/// - Can only succeed if the provided treasury is valid. -/// -/// Discussion: -/// - Ore subdivides into 1 billion indivisible atomic units. Therefore if global hashpower -/// were to increase to the point where >1B valid hashes were submitted to the protocol for -/// validation per epoch, the Ore inflation rate could be pushed above the 1 ORE / min target. -/// - The strict limits on bus reward counters guarantee inflation can never exceed 2 ORE / min, -/// but it is the responsibility of the admin to adjust mining difficulty if needed to maintain -/// the 1 ORE / min target average. -/// - It is worth noting that Solana today processes well below 1 million real TPS or -/// (60 * 1,000,000) = 60,000,000 transactions per minute. Even if every transaction on Solana -/// were a mine operation, this would still be two orders of magnitude below the boundary -/// condition where Ore inflation targets would be challenged. So in practice, Solana is likely -/// to reach its network saturation point long before Ore ever hits its theoretical limits. -pub fn process_update_difficulty<'a, 'info>( - _program_id: &Pubkey, - accounts: &'a [AccountInfo<'info>], - data: &[u8], -) -> ProgramResult { - // Parse args - let args = UpdateDifficultyArgs::try_from_bytes(data)?; - - // Load accounts - let [signer, treasury_info] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - load_signer(signer)?; - load_treasury(treasury_info, true)?; - - // Validate signer is admin - let mut treasury_data = treasury_info.data.borrow_mut(); - let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; - if treasury.admin.ne(&signer.key) { - return Err(ProgramError::MissingRequiredSignature); - } - - // Update admin - treasury.difficulty = args.new_difficulty; - - Ok(()) -} diff --git a/src/state/hash.rs b/src/state/hash.rs deleted file mode 100644 index 4c4f89c..0000000 --- a/src/state/hash.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::{fmt, mem::transmute}; - -use bytemuck::{Pod, Zeroable}; -use solana_program::keccak::{Hash as KeccakHash, HASH_BYTES}; - -use crate::impl_to_bytes; - -/// Hash is an equivalent type to solana_program::keccak::Hash which supports bytemuck serialization. -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] -pub struct Hash(pub [u8; HASH_BYTES]); - -impl From for Hash { - #[inline(always)] - fn from(value: KeccakHash) -> Self { - unsafe { transmute(value) } - } -} - -impl From for KeccakHash { - #[inline(always)] - fn from(value: Hash) -> Self { - unsafe { transmute(value) } - } -} - -impl fmt::Display for Hash { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", bs58::encode(self.0).into_string()) - } -} - -impl_to_bytes!(Hash); diff --git a/src/state/mod.rs b/src/state/mod.rs deleted file mode 100644 index 64809b4..0000000 --- a/src/state/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod bus; -mod hash; -mod proof; -mod treasury; - -pub use bus::*; -pub use hash::*; -pub use proof::*; -pub use treasury::*; diff --git a/src/state/proof.rs b/src/state/proof.rs deleted file mode 100644 index f3c1cc2..0000000 --- a/src/state/proof.rs +++ /dev/null @@ -1,39 +0,0 @@ -use bytemuck::{Pod, Zeroable}; -use shank::ShankAccount; -use solana_program::pubkey::Pubkey; - -use crate::{ - impl_account_from_bytes, impl_to_bytes, - state::Hash, - utils::{AccountDiscriminator, Discriminator}, -}; - -/// Proof accounts track a miner's current hash, claimable rewards, and lifetime stats. -/// Every miner is allowed one proof account which is required by the program to mine or claim rewards. -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)] -pub struct Proof { - /// The account (i.e. miner) authorized to use this proof. - pub authority: Pubkey, - - /// The quantity of tokens this miner may claim from the treasury. - pub claimable_rewards: u64, - - /// The proof's current hash. - pub hash: Hash, - - /// The total lifetime hashes provided by this miner. - pub total_hashes: u64, - - /// The total lifetime rewards distributed to this miner. - pub total_rewards: u64, -} - -impl Discriminator for Proof { - fn discriminator() -> AccountDiscriminator { - AccountDiscriminator::Proof - } -} - -impl_to_bytes!(Proof); -impl_account_from_bytes!(Proof); diff --git a/src/state/treasury.rs b/src/state/treasury.rs deleted file mode 100644 index 9ffbfc5..0000000 --- a/src/state/treasury.rs +++ /dev/null @@ -1,42 +0,0 @@ -use bytemuck::{Pod, Zeroable}; -use shank::ShankAccount; -use solana_program::pubkey::Pubkey; - -use crate::{ - impl_account_from_bytes, impl_to_bytes, - state::Hash, - utils::{AccountDiscriminator, Discriminator}, -}; - -/// Treasury is a singleton account which manages all program wide variables. -/// It is the mint authority for the Ore token and also the authority of the program-owned token account. -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)] -pub struct Treasury { - /// The admin authority with permission to update the difficulty. - pub admin: Pubkey, - - /// The bump of the treasury account PDA, for signing CPIs. - pub bump: u64, - - /// The hash difficulty. - pub difficulty: Hash, - - /// The timestamp of the reset invocation. - pub last_reset_at: i64, - - /// The reward rate to payout to miners for submiting valid hashes. - pub reward_rate: u64, - - /// The total lifetime claimed rewards. - pub total_claimed_rewards: u64, -} - -impl Discriminator for Treasury { - fn discriminator() -> AccountDiscriminator { - AccountDiscriminator::Treasury - } -} - -impl_to_bytes!(Treasury); -impl_account_from_bytes!(Treasury); diff --git a/stake/api/Cargo.toml b/stake/api/Cargo.toml new file mode 100644 index 0000000..e69de29 diff --git a/stake/api/src/lib.rs b/stake/api/src/lib.rs new file mode 100644 index 0000000..e69de29 diff --git a/stake/program/Cargo.toml b/stake/program/Cargo.toml new file mode 100644 index 0000000..e69de29 diff --git a/stake/program/src/lib.rs b/stake/program/src/lib.rs new file mode 100644 index 0000000..e69de29 diff --git a/tests/buffers/metadata_program.bpf b/tests/buffers/metadata_program.bpf deleted file mode 100644 index 3ebd1b6..0000000 Binary files a/tests/buffers/metadata_program.bpf and /dev/null differ diff --git a/tests/test_initialize.rs b/tests/test_initialize.rs deleted file mode 100644 index 66afdd9..0000000 --- a/tests/test_initialize.rs +++ /dev/null @@ -1,170 +0,0 @@ -use mpl_token_metadata::{ - accounts::Metadata, - types::{Key, TokenStandard}, -}; -use ore::{ - state::{Bus, Treasury}, - utils::AccountDeserialize, - BUS_ADDRESSES, BUS_COUNT, INITIAL_DIFFICULTY, INITIAL_REWARD_RATE, METADATA_ADDRESS, - METADATA_NAME, METADATA_SYMBOL, METADATA_URI, MINT_ADDRESS, TREASURY, -}; -use solana_program::{ - hash::Hash, program_option::COption, program_pack::Pack, pubkey::Pubkey, rent::Rent, -}; -use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; -use solana_sdk::{ - account::Account, - signature::{Keypair, Signer}, - transaction::Transaction, -}; -use spl_token::state::{AccountState, Mint}; - -#[tokio::test] -async fn test_initialize() { - // Setup - let (mut banks, payer, blockhash) = setup_program_test_env().await; - - // Pdas - let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); - let treasury_tokens_address = - spl_associated_token_account::get_associated_token_address(&treasury_pda.0, &MINT_ADDRESS); - - // Submit tx - let ix = ore::instruction::initialize(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Test bus state - for i in 0..BUS_COUNT { - let bus_account = banks.get_account(BUS_ADDRESSES[i]).await.unwrap().unwrap(); - assert_eq!(bus_account.owner, ore::id()); - let bus = Bus::try_from_bytes(&bus_account.data).unwrap(); - assert_eq!(bus.id as u8, i as u8); - assert_eq!(bus.rewards, 0); - } - - // Test treasury state - let treasury_account = banks.get_account(treasury_pda.0).await.unwrap().unwrap(); - assert_eq!(treasury_account.owner, ore::id()); - let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); - assert_eq!(treasury.bump as u8, treasury_pda.1); - assert_eq!(treasury.admin, payer.pubkey()); - assert_eq!(treasury.difficulty, INITIAL_DIFFICULTY.into()); - assert_eq!(treasury.last_reset_at as u8, 0); - assert_eq!(treasury.reward_rate, INITIAL_REWARD_RATE); - assert_eq!(treasury.total_claimed_rewards as u8, 0); - - // Test mint state - let mint_account = banks.get_account(MINT_ADDRESS).await.unwrap().unwrap(); - assert_eq!(mint_account.owner, spl_token::id()); - let mint = Mint::unpack(&mint_account.data).unwrap(); - assert_eq!(mint.mint_authority, COption::Some(treasury_pda.0)); - assert_eq!(mint.supply, 0); - assert_eq!(mint.decimals, ore::TOKEN_DECIMALS); - assert_eq!(mint.is_initialized, true); - assert_eq!(mint.freeze_authority, COption::None); - - // Test metadata state - let metadata_account = banks.get_account(METADATA_ADDRESS).await.unwrap().unwrap(); - assert_eq!(metadata_account.owner, mpl_token_metadata::ID); - let metadata = Metadata::from_bytes(&metadata_account.data).unwrap(); - assert_eq!(metadata.key, Key::MetadataV1); - assert_eq!(metadata.update_authority, payer.pubkey()); - assert_eq!(metadata.mint, MINT_ADDRESS); - assert_eq!(metadata.name.trim_end_matches('\0'), METADATA_NAME); - assert_eq!(metadata.symbol.trim_end_matches('\0'), METADATA_SYMBOL); - assert_eq!(metadata.uri.trim_end_matches('\0'), METADATA_URI); - assert_eq!(metadata.seller_fee_basis_points, 0); - assert_eq!(metadata.creators, None); - assert_eq!(metadata.primary_sale_happened, false); - assert_eq!(metadata.is_mutable, true); - assert_eq!(metadata.token_standard, Some(TokenStandard::Fungible)); - assert_eq!(metadata.collection, None); - assert_eq!(metadata.uses, None); - assert_eq!(metadata.collection_details, None); - assert_eq!(metadata.programmable_config, None); - - // Test treasury token state - let treasury_tokens_account = banks - .get_account(treasury_tokens_address) - .await - .unwrap() - .unwrap(); - assert_eq!(treasury_tokens_account.owner, spl_token::id()); - let treasury_tokens = spl_token::state::Account::unpack(&treasury_tokens_account.data).unwrap(); - assert_eq!(treasury_tokens.mint, MINT_ADDRESS); - assert_eq!(treasury_tokens.owner, treasury_pda.0); - assert_eq!(treasury_tokens.amount, 0); - assert_eq!(treasury_tokens.delegate, COption::None); - assert_eq!(treasury_tokens.state, AccountState::Initialized); - assert_eq!(treasury_tokens.is_native, COption::None); - assert_eq!(treasury_tokens.delegated_amount, 0); - assert_eq!(treasury_tokens.close_authority, COption::None); -} - -#[tokio::test] -async fn test_initialize_not_enough_accounts() { - // Setup - let (mut banks, payer, blockhash) = setup_program_test_env().await; - - // Submit tx - let mut ix = ore::instruction::initialize(payer.pubkey()); - ix.accounts.remove(1); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_initialize_bad_key() { - // Setup - let (mut banks, payer, blockhash) = setup_program_test_env().await; - - // Bad addresses - let bad_pda = Pubkey::find_program_address(&[b"t"], &ore::id()); - for i in 1..12 { - let mut ix = ore::instruction::initialize(payer.pubkey()); - ix.accounts[i].pubkey = bad_pda.0; - let tx = - Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); - } -} - -#[tokio::test] -async fn test_initialize_bad_programs() { - // Setup - let (mut banks, payer, blockhash) = setup_program_test_env().await; - - // Bad addresses - for i in 13..18 { - let mut ix = ore::instruction::initialize(payer.pubkey()); - ix.accounts[i].pubkey = Pubkey::new_unique(); - let tx = - Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); - } -} - -async fn setup_program_test_env() -> (BanksClient, Keypair, Hash) { - let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); - program_test.prefer_bpf(true); - - // Setup metadata program - let data = read_file(&"tests/buffers/metadata_program.bpf"); - program_test.add_account( - mpl_token_metadata::ID, - Account { - lamports: Rent::default().minimum_balance(data.len()).max(1), - data, - owner: solana_sdk::bpf_loader::id(), - executable: true, - rent_epoch: 0, - }, - ); - - program_test.start().await -} diff --git a/tests/test_mine.rs b/tests/test_mine.rs deleted file mode 100644 index 727eb2a..0000000 --- a/tests/test_mine.rs +++ /dev/null @@ -1,774 +0,0 @@ -use std::{mem::size_of, str::FromStr}; - -use ore::{ - instruction::{MineArgs, OreInstruction}, - state::{Bus, Proof, Treasury}, - utils::{AccountDeserialize, Discriminator}, - BUS_ADDRESSES, BUS_COUNT, EPOCH_DURATION, INITIAL_REWARD_RATE, MINT_ADDRESS, PROOF, START_AT, - TOKEN_DECIMALS, TREASURY, TREASURY_ADDRESS, -}; -use rand::{distributions::Uniform, Rng}; -use solana_program::{ - clock::Clock, - epoch_schedule::DEFAULT_SLOTS_PER_EPOCH, - hash::Hash, - instruction::{AccountMeta, Instruction}, - keccak::{hashv, Hash as KeccakHash}, - native_token::LAMPORTS_PER_SOL, - program_option::COption, - program_pack::Pack, - pubkey::Pubkey, - slot_hashes::SlotHash, - system_program, sysvar, -}; -use solana_program_test::{processor, BanksClient, ProgramTest}; -use solana_sdk::{ - account::Account, - signature::{Keypair, Signer}, - transaction::Transaction, -}; -use spl_associated_token_account::{ - get_associated_token_address, instruction::create_associated_token_account, -}; -use spl_token::state::{AccountState, Mint}; - -#[tokio::test] -async fn test_mine() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - - // Submit register tx - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Assert proof state - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - assert_eq!(proof_account.owner, ore::id()); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - assert_eq!(proof.authority, payer.pubkey()); - assert_eq!(proof.claimable_rewards, 0); - assert_eq!(proof.hash, hashv(&[payer.pubkey().as_ref()]).into()); - assert_eq!(proof.total_hashes, 0); - assert_eq!(proof.total_rewards, 0); - - // Find next hash - let (next_hash, nonce) = find_next_hash( - proof.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer.pubkey(), - ); - - // Submit mine tx - let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], next_hash.into(), nonce); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Assert proof state - let slot_hashes_account = banks - .get_account(sysvar::slot_hashes::id()) - .await - .unwrap() - .unwrap(); - let slot_hash_bytes = &slot_hashes_account.data[0..size_of::()]; - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - assert_eq!(proof_account.owner, ore::id()); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - assert_eq!(proof.authority, payer.pubkey()); - assert_eq!(proof.claimable_rewards, INITIAL_REWARD_RATE); - assert_eq!( - proof.hash, - hashv(&[&next_hash.as_ref(), slot_hash_bytes,]).into() - ); - assert_eq!(proof.total_hashes, 1); - assert_eq!(proof.total_rewards, INITIAL_REWARD_RATE); - - // Submit claim tx - let amount = proof.claimable_rewards; - let beneficiary_address = get_associated_token_address(&payer.pubkey(), &ore::MINT_ADDRESS); - let token_ix = create_associated_token_account( - &payer.pubkey(), - &payer.pubkey(), - &ore::MINT_ADDRESS, - &spl_token::id(), - ); - let ix = ore::instruction::claim(payer.pubkey(), beneficiary_address, amount); - let tx = Transaction::new_signed_with_payer( - &[token_ix, ix], - Some(&payer.pubkey()), - &[&payer], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Assert proof state - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - let proof_ = Proof::try_from_bytes(&proof_account.data).unwrap(); - assert_eq!(proof_.authority, proof.authority); - assert_eq!(proof_.claimable_rewards, 0); - assert_eq!(proof_.hash, proof.hash); - assert_eq!(proof_.total_hashes, proof.total_hashes); - assert_eq!(proof_.total_rewards, proof.total_rewards); - - // Assert beneficiary state - let beneficiary_account = banks - .get_account(beneficiary_address) - .await - .unwrap() - .unwrap(); - assert_eq!(beneficiary_account.owner, spl_token::id()); - let beneficiary = spl_token::state::Account::unpack(&beneficiary_account.data).unwrap(); - assert_eq!(beneficiary.mint, ore::MINT_ADDRESS); - assert_eq!(beneficiary.owner, payer.pubkey()); - assert_eq!(beneficiary.amount, amount); - assert_eq!(beneficiary.delegate, COption::None); - assert_eq!(beneficiary.state, AccountState::Initialized); - assert_eq!(beneficiary.is_native, COption::None); - assert_eq!(beneficiary.delegated_amount, 0); - assert_eq!(beneficiary.close_authority, COption::None); -} - -#[tokio::test] -async fn test_mine_alt_proof() { - // Setup - let (mut banks, payer, payer_alt, blockhash) = - setup_program_test_env(true, ClockState::Normal).await; - - // Submit register tx - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Submit register alt tx - let proof_alt_pda = - Pubkey::find_program_address(&[PROOF, payer_alt.pubkey().as_ref()], &ore::id()); - let ix_alt = ore::instruction::register(payer_alt.pubkey()); - let tx = Transaction::new_signed_with_payer( - &[ix_alt], - Some(&payer_alt.pubkey()), - &[&payer_alt], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Submit mine tx with invalid proof - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (next_hash, nonce) = find_next_hash( - proof.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer.pubkey(), - ); - let ix = Instruction { - program_id: ore::id(), - accounts: vec![ - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new(BUS_ADDRESSES[0], false), - AccountMeta::new(proof_alt_pda.0, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), - ], - data: [ - OreInstruction::Mine.to_vec(), - MineArgs { - hash: next_hash.into(), - nonce: nonce.to_le_bytes(), - } - .to_bytes() - .to_vec(), - ] - .concat(), - }; - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_mine_correct_hash_alt_proof() { - // Setup - let (mut banks, payer, payer_alt, blockhash) = - setup_program_test_env(true, ClockState::Normal).await; - - // Submit register alt tx - let proof_alt_pda = - Pubkey::find_program_address(&[PROOF, payer_alt.pubkey().as_ref()], &ore::id()); - let ix_alt = ore::instruction::register(payer_alt.pubkey()); - let tx = Transaction::new_signed_with_payer( - &[ix_alt], - Some(&payer_alt.pubkey()), - &[&payer_alt], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Submit with correct hash for invalid proof - let proof_alt_account = banks.get_account(proof_alt_pda.0).await.unwrap().unwrap(); - let proof_alt = Proof::try_from_bytes(&proof_alt_account.data).unwrap(); - let (next_hash, nonce) = find_next_hash( - proof_alt.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer_alt.pubkey(), - ); - let ix = Instruction { - program_id: ore::id(), - accounts: vec![ - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new(BUS_ADDRESSES[0], false), - AccountMeta::new(proof_alt_pda.0, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), - ], - data: [ - OreInstruction::Mine.to_vec(), - MineArgs { - hash: next_hash.into(), - nonce: nonce.to_le_bytes(), - } - .to_bytes() - .to_vec(), - ] - .concat(), - }; - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_mine_bus_rewards_insufficient() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(false, ClockState::Normal).await; - - // Submit register tx - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Find next hash - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (next_hash, nonce) = find_next_hash( - proof.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer.pubkey(), - ); - - // Submit mine tx - let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], next_hash.into(), nonce); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_claim_too_large() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - - // Submit register tx - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Submit claim tx - let beneficiary = get_associated_token_address(&payer.pubkey(), &ore::MINT_ADDRESS); - let token_ix = create_associated_token_account( - &payer.pubkey(), - &payer.pubkey(), - &ore::MINT_ADDRESS, - &spl_token::id(), - ); - let ix = ore::instruction::claim(payer.pubkey(), beneficiary, 1); - let tx = Transaction::new_signed_with_payer( - &[token_ix, ix], - Some(&payer.pubkey()), - &[&payer], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_claim_other_proof() { - // Setup - let (mut banks, payer, alt_payer, blockhash) = - setup_program_test_env(true, ClockState::Normal).await; - - // Submit register tx - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Submit claim tx - let beneficiary = get_associated_token_address(&alt_payer.pubkey(), &ore::MINT_ADDRESS); - let token_ix = create_associated_token_account( - &alt_payer.pubkey(), - &alt_payer.pubkey(), - &ore::MINT_ADDRESS, - &spl_token::id(), - ); - let mut ix = ore::instruction::claim(payer.pubkey(), beneficiary, 0); - ix.accounts[0].pubkey = alt_payer.pubkey(); - let tx = Transaction::new_signed_with_payer( - &[token_ix, ix], - Some(&alt_payer.pubkey()), - &[&alt_payer], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_mine_not_enough_accounts() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - - // Submit register tx - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Find next hash - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (next_hash, nonce) = find_next_hash( - proof.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer.pubkey(), - ); - - // Submit mine tx - let mut ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], next_hash.into(), nonce); - ix.accounts.remove(1); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_mine_too_early() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::TooEarly).await; - - // Submit register tx - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Find next hash - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (next_hash, nonce) = find_next_hash( - proof.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer.pubkey(), - ); - - // Submit mine tx - let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], next_hash.into(), nonce); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_mine_needs_reset() { - // Setup - let (mut banks, payer, _, blockhash) = - setup_program_test_env(true, ClockState::NeedsReset).await; - - // Submit register tx - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Find next hash - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (next_hash, nonce) = find_next_hash( - proof.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer.pubkey(), - ); - - // Submit mine tx - let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], next_hash.into(), nonce); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_claim_not_enough_accounts() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - - // Submit register tx - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Submit claim tx - let beneficiary = get_associated_token_address(&payer.pubkey(), &ore::MINT_ADDRESS); - let token_ix = create_associated_token_account( - &payer.pubkey(), - &payer.pubkey(), - &ore::MINT_ADDRESS, - &spl_token::id(), - ); - let mut ix = ore::instruction::claim(payer.pubkey(), beneficiary, 0); - ix.accounts.remove(1); - let tx = Transaction::new_signed_with_payer( - &[token_ix, ix], - Some(&payer.pubkey()), - &[&payer], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_mine_fail_bad_data() { - // Setup - const FUZZ: usize = 10; - let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - - // Submit register tx - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Get proof - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - - // Shared variables for tests. - let mut rng = rand::thread_rng(); - let (next_hash, nonce) = find_next_hash( - proof.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer.pubkey(), - ); - let signer = payer.pubkey(); - let proof_address = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &ore::id()).0; - - // Fuzz randomized instruction data - for _ in 0..FUZZ { - let length_range = Uniform::from(5..=256); - let length = rng.sample(length_range); - let random_bytes: Vec = (0..length).map(|_| rng.gen()).collect(); - let ix = Instruction { - program_id: ore::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(BUS_ADDRESSES[0], false), - AccountMeta::new(proof_address, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), - ], - data: [OreInstruction::Mine.to_vec(), random_bytes].concat(), - }; - let tx = - Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); - } - - // Fuzz test random hashes and nonces - for _ in 0..FUZZ { - let next_hash = KeccakHash::new_unique(); - let nonce: u64 = rng.gen(); - assert_mine_tx_err( - &mut banks, - &payer, - blockhash, - payer.pubkey(), - BUS_ADDRESSES[0], - proof_address, - TREASURY_ADDRESS, - sysvar::slot_hashes::id(), - next_hash, - nonce, - ) - .await; - } - - // Fuzz test random bus addresses - for _ in 0..FUZZ { - assert_mine_tx_err( - &mut banks, - &payer, - blockhash, - payer.pubkey(), - Pubkey::new_unique(), - proof_address, - TREASURY_ADDRESS, - sysvar::slot_hashes::id(), - next_hash, - nonce, - ) - .await; - } - - // Fuzz test random proof addresses - for _ in 0..FUZZ { - assert_mine_tx_err( - &mut banks, - &payer, - blockhash, - payer.pubkey(), - BUS_ADDRESSES[0], - Pubkey::new_unique(), - TREASURY_ADDRESS, - sysvar::slot_hashes::id(), - next_hash, - nonce, - ) - .await; - } - - // Mix up the proof and treasury addresses - assert_mine_tx_err( - &mut banks, - &payer, - blockhash, - payer.pubkey(), - BUS_ADDRESSES[0], - TREASURY_ADDRESS, - proof_address, - sysvar::slot_hashes::id(), - next_hash, - nonce, - ) - .await; - - // Pass an invalid sysvar - assert_mine_tx_err( - &mut banks, - &payer, - blockhash, - payer.pubkey(), - BUS_ADDRESSES[0], - proof_address, - TREASURY_ADDRESS, - sysvar::clock::id(), - next_hash, - nonce, - ) - .await; -} - -async fn assert_mine_tx_err( - banks: &mut BanksClient, - payer: &Keypair, - blockhash: Hash, - signer: Pubkey, - bus: Pubkey, - proof: Pubkey, - treasury: Pubkey, - slot_hash: Pubkey, - next_hash: KeccakHash, - nonce: u64, -) { - let ix = Instruction { - program_id: ore::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus, false), - AccountMeta::new(proof, false), - AccountMeta::new(treasury, false), - AccountMeta::new_readonly(slot_hash, false), - ], - data: [ - OreInstruction::Mine.to_vec(), - MineArgs { - hash: next_hash.into(), - nonce: nonce.to_le_bytes(), - } - .to_bytes() - .to_vec(), - ] - .concat(), - }; - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -fn find_next_hash(hash: KeccakHash, difficulty: KeccakHash, signer: Pubkey) -> (KeccakHash, u64) { - let mut next_hash: KeccakHash; - let mut nonce = 0u64; - loop { - next_hash = hashv(&[ - hash.to_bytes().as_slice(), - signer.to_bytes().as_slice(), - nonce.to_le_bytes().as_slice(), - ]); - if next_hash.le(&difficulty) { - break; - } else { - println!("Invalid hash: {} Nonce: {:?}", next_hash.to_string(), nonce); - } - nonce += 1; - } - (next_hash, nonce) -} - -enum ClockState { - Normal, - TooEarly, - NeedsReset, -} - -async fn setup_program_test_env( - funded_busses: bool, - clock_state: ClockState, -) -> (BanksClient, Keypair, Keypair, solana_program::hash::Hash) { - let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); - program_test.prefer_bpf(true); - - // Busses - for i in 0..BUS_COUNT { - program_test.add_account_with_base64_data( - BUS_ADDRESSES[i], - 1057920, - ore::id(), - bs64::encode( - &[ - &(Bus::discriminator() as u64).to_le_bytes(), - Bus { - id: i as u64, - rewards: if funded_busses { 250_000_000 } else { 0 }, - } - .to_bytes(), - ] - .concat(), - ) - .as_str(), - ); - } - - // Treasury - let admin_address = Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap(); - let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); - program_test.add_account_with_base64_data( - treasury_pda.0, - 1614720, - ore::id(), - bs64::encode( - &[ - &(Treasury::discriminator() as u64).to_le_bytes(), - Treasury { - bump: treasury_pda.1 as u64, - admin: admin_address, - difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), - last_reset_at: START_AT, - reward_rate: INITIAL_REWARD_RATE, - total_claimed_rewards: 0, - } - .to_bytes(), - ] - .concat(), - ) - .as_str(), - ); - - // Mint - let mut mint_src: [u8; Mint::LEN] = [0; Mint::LEN]; - Mint { - mint_authority: COption::Some(TREASURY_ADDRESS), - supply: 2_000_000_000, - decimals: TOKEN_DECIMALS, - is_initialized: true, - freeze_authority: COption::None, - } - .pack_into_slice(&mut mint_src); - program_test.add_account_with_base64_data( - MINT_ADDRESS, - 1461600, - spl_token::id(), - bs64::encode(&mint_src).as_str(), - ); - - // Treasury tokens - let tokens_address = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - let mut tokens_src: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; - spl_token::state::Account { - mint: MINT_ADDRESS, - owner: TREASURY_ADDRESS, - amount: 2_000_000_000, - delegate: COption::None, - state: AccountState::Initialized, - is_native: COption::None, - delegated_amount: 0, - close_authority: COption::None, - } - .pack_into_slice(&mut tokens_src); - program_test.add_account_with_base64_data( - tokens_address, - 2039280, - spl_token::id(), - bs64::encode(&tokens_src).as_str(), - ); - - // Set sysvar - let ts = match clock_state { - ClockState::Normal => START_AT + 1, - ClockState::TooEarly => START_AT - 1, - ClockState::NeedsReset => START_AT + EPOCH_DURATION, - }; - program_test.add_sysvar_account( - sysvar::clock::id(), - &Clock { - slot: 0, - epoch_start_timestamp: 0, - epoch: 0, - leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, - unix_timestamp: ts, - }, - ); - - // Setup alt payer - let payer_alt = Keypair::new(); - program_test.add_account( - payer_alt.pubkey(), - Account { - lamports: LAMPORTS_PER_SOL, - data: vec![], - owner: system_program::id(), - executable: false, - rent_epoch: 0, - }, - ); - - let (banks, payer, blockhash) = program_test.start().await; - (banks, payer, payer_alt, blockhash) -} diff --git a/tests/test_register.rs b/tests/test_register.rs deleted file mode 100644 index 454baa2..0000000 --- a/tests/test_register.rs +++ /dev/null @@ -1,188 +0,0 @@ -use std::str::FromStr; - -use ore::{ - instruction::{register, OreInstruction, RegisterArgs}, - state::{Bus, Treasury}, - utils::Discriminator, - BUS_ADDRESSES, BUS_COUNT, INITIAL_REWARD_RATE, MINT_ADDRESS, PROOF, TOKEN_DECIMALS, TREASURY, - TREASURY_ADDRESS, -}; -use solana_program::{ - clock::Clock, - epoch_schedule::DEFAULT_SLOTS_PER_EPOCH, - instruction::{AccountMeta, Instruction}, - keccak::Hash as KeccakHash, - program_option::COption, - program_pack::Pack, - pubkey::Pubkey, - rent::Rent, - sysvar, -}; -use solana_program_test::{processor, BanksClient, ProgramTest}; -use solana_sdk::{ - signature::{Keypair, Signer}, - system_transaction::transfer, - transaction::Transaction, -}; -use spl_token::state::{AccountState, Mint}; - -#[tokio::test] -async fn test_register_account_with_lamports() { - let (mut banks, payer, blockhash) = setup_program_test_env().await; - - // Send lamports to the proof pda - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let lamports = Rent::default().minimum_balance(0); - let tx = transfer(&payer, &proof_pda.0, lamports, blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Assert register succeeds - let ix = register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); -} - -#[tokio::test] -async fn test_register_not_enough_accounts() { - let (mut banks, payer, blockhash) = setup_program_test_env().await; - - // Assert register fails - let mut ix = register(payer.pubkey()); - ix.accounts.remove(1); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_register_fail_other() { - let (mut banks, payer, blockhash) = setup_program_test_env().await; - - // Try register for another keypair - let other = Keypair::new(); - let proof_pda = Pubkey::find_program_address(&[PROOF, other.pubkey().as_ref()], &ore::id()); - let ix = Instruction { - program_id: ore::id(), - accounts: vec![ - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new(proof_pda.0, false), - AccountMeta::new_readonly(solana_program::system_program::id(), false), - ], - data: [ - OreInstruction::Register.to_vec(), - RegisterArgs { bump: proof_pda.1 }.to_bytes().to_vec(), - ] - .concat(), - }; - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -async fn setup_program_test_env() -> (BanksClient, Keypair, solana_program::hash::Hash) { - let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); - program_test.prefer_bpf(true); - - // Busses - for i in 0..BUS_COUNT { - program_test.add_account_with_base64_data( - BUS_ADDRESSES[i], - 1057920, - ore::id(), - bs64::encode( - &[ - &(Bus::discriminator() as u64).to_le_bytes(), - Bus { - id: i as u64, - rewards: 250_000_000, - } - .to_bytes(), - ] - .concat(), - ) - .as_str(), - ); - } - - // Treasury - let admin_address = Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap(); - let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); - program_test.add_account_with_base64_data( - treasury_pda.0, - 1614720, - ore::id(), - bs64::encode( - &[ - &(Treasury::discriminator() as u64).to_le_bytes(), - Treasury { - bump: treasury_pda.1 as u64, - admin: admin_address, - difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), - last_reset_at: 100, - reward_rate: INITIAL_REWARD_RATE, - total_claimed_rewards: 0, - } - .to_bytes(), - ] - .concat(), - ) - .as_str(), - ); - - // Mint - let mut mint_src: [u8; Mint::LEN] = [0; Mint::LEN]; - Mint { - mint_authority: COption::Some(TREASURY_ADDRESS), - supply: 2_000_000_000, - decimals: TOKEN_DECIMALS, - is_initialized: true, - freeze_authority: COption::None, - } - .pack_into_slice(&mut mint_src); - program_test.add_account_with_base64_data( - MINT_ADDRESS, - 1461600, - spl_token::id(), - bs64::encode(&mint_src).as_str(), - ); - - // Treasury tokens - let tokens_address = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - let mut tokens_src: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; - spl_token::state::Account { - mint: MINT_ADDRESS, - owner: TREASURY_ADDRESS, - amount: 2_000_000_000, - delegate: COption::None, - state: AccountState::Initialized, - is_native: COption::None, - delegated_amount: 0, - close_authority: COption::None, - } - .pack_into_slice(&mut tokens_src); - program_test.add_account_with_base64_data( - tokens_address, - 2039280, - spl_token::id(), - bs64::encode(&tokens_src).as_str(), - ); - - // Set sysvar - program_test.add_sysvar_account( - sysvar::clock::id(), - &Clock { - slot: 0, - epoch_start_timestamp: 0, - epoch: 0, - leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, - unix_timestamp: 0, - }, - ); - - program_test.start().await -} diff --git a/tests/test_reset.rs b/tests/test_reset.rs deleted file mode 100644 index e0355c2..0000000 --- a/tests/test_reset.rs +++ /dev/null @@ -1,440 +0,0 @@ -use std::str::FromStr; - -use ore::{ - instruction::OreInstruction, - state::{Bus, Treasury}, - utils::{AccountDeserialize, Discriminator}, - BUS, BUS_ADDRESSES, BUS_COUNT, BUS_EPOCH_REWARDS, INITIAL_DIFFICULTY, INITIAL_REWARD_RATE, - MAX_EPOCH_REWARDS, MINT_ADDRESS, START_AT, TOKEN_DECIMALS, TREASURY, TREASURY_ADDRESS, -}; -use rand::seq::SliceRandom; -use solana_program::{ - clock::Clock, - epoch_schedule::DEFAULT_SLOTS_PER_EPOCH, - hash::Hash, - instruction::{AccountMeta, Instruction}, - native_token::LAMPORTS_PER_SOL, - program_option::COption, - program_pack::Pack, - pubkey::Pubkey, - system_program, sysvar, -}; -use solana_program_test::{processor, BanksClient, ProgramTest}; -use solana_sdk::{ - account::Account, - signature::{Keypair, Signer}, - transaction::Transaction, -}; -use spl_token::state::{AccountState, Mint}; - -#[tokio::test] -async fn test_reset() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - - // Pdas - let bus_pdas = vec![ - Pubkey::find_program_address(&[BUS, &[0]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[1]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[2]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[3]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[4]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[5]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[6]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[7]], &ore::id()), - ]; - // let mint_pda = Pubkey::find_program_address(&[MINT], &ore::id()); - let treasury_tokens_address = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - - // Submit tx - let ix = ore::instruction::reset(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Test bus state - for i in 0..BUS_COUNT { - let bus_account = banks.get_account(bus_pdas[i].0).await.unwrap().unwrap(); - assert_eq!(bus_account.owner, ore::id()); - let bus = Bus::try_from_bytes(&bus_account.data).unwrap(); - assert_eq!(bus.id as u8, i as u8); - assert_eq!(bus.rewards, BUS_EPOCH_REWARDS); - } - - // Test treasury state - let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); - assert_eq!(treasury_account.owner, ore::id()); - let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); - assert_eq!( - treasury.admin, - Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap() - ); - assert_eq!(treasury.difficulty, INITIAL_DIFFICULTY.into()); - assert_eq!(treasury.last_reset_at, START_AT + 1); - assert_eq!(treasury.reward_rate, INITIAL_REWARD_RATE.saturating_div(2)); - assert_eq!(treasury.total_claimed_rewards as u8, 0); - - // Test mint state - let mint_account = banks.get_account(MINT_ADDRESS).await.unwrap().unwrap(); - assert_eq!(mint_account.owner, spl_token::id()); - let mint = Mint::unpack(&mint_account.data).unwrap(); - assert_eq!(mint.mint_authority, COption::Some(TREASURY_ADDRESS)); - assert_eq!(mint.supply, MAX_EPOCH_REWARDS); - assert_eq!(mint.decimals, ore::TOKEN_DECIMALS); - assert_eq!(mint.is_initialized, true); - assert_eq!(mint.freeze_authority, COption::None); - - // Test treasury token state - let treasury_tokens_account = banks - .get_account(treasury_tokens_address) - .await - .unwrap() - .unwrap(); - assert_eq!(treasury_tokens_account.owner, spl_token::id()); - let treasury_tokens = spl_token::state::Account::unpack(&treasury_tokens_account.data).unwrap(); - assert_eq!(treasury_tokens.mint, MINT_ADDRESS); - assert_eq!(treasury_tokens.owner, TREASURY_ADDRESS); - assert_eq!(treasury_tokens.amount, MAX_EPOCH_REWARDS); - assert_eq!(treasury_tokens.delegate, COption::None); - assert_eq!(treasury_tokens.state, AccountState::Initialized); - assert_eq!(treasury_tokens.is_native, COption::None); - assert_eq!(treasury_tokens.delegated_amount, 0); - assert_eq!(treasury_tokens.close_authority, COption::None); -} - -#[tokio::test] -async fn test_reset_bad_key() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - - // Bad addresses - let bad_pda = Pubkey::find_program_address(&[b"t"], &ore::id()); - for i in 1..13 { - let mut ix = ore::instruction::reset(payer.pubkey()); - ix.accounts[i].pubkey = bad_pda.0; - let tx = - Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); - } -} - -#[tokio::test] -async fn test_reset_busses_out_of_order_fail() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - - // Pdas - let signer = payer.pubkey(); - let bus_pdas = vec![ - Pubkey::find_program_address(&[BUS, &[5]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[0]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[6]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[2]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[3]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[7]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[1]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[4]], &ore::id()), - ]; - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - - // Submit tx - let ix = Instruction { - program_id: ore::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus_pdas[0].0, false), - AccountMeta::new(bus_pdas[1].0, false), - AccountMeta::new(bus_pdas[2].0, false), - AccountMeta::new(bus_pdas[3].0, false), - AccountMeta::new(bus_pdas[4].0, false), - AccountMeta::new(bus_pdas[5].0, false), - AccountMeta::new(bus_pdas[6].0, false), - AccountMeta::new(bus_pdas[7].0, false), - AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: OreInstruction::Reset.to_vec(), - }; - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_reset_race() { - // Setup - let (mut banks, payer, payer_alt, blockhash) = setup_program_test_env(ClockState::Normal).await; - - // Reset one passes - let ix = ore::instruction::reset(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Reset two fails - let ix = ore::instruction::reset(payer_alt.pubkey()); - let tx = Transaction::new_signed_with_payer( - &[ix], - Some(&payer_alt.pubkey()), - &[&payer_alt], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_reset_too_early() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::TooEarly).await; - - // Reset one passes - let ix = ore::instruction::reset(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_reset_not_enough_keys() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - - // Reset with missing account - let mut ix = ore::instruction::reset(payer.pubkey()); - ix.accounts.remove(1); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_reset_busses_duplicate_fail() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - - // Pdas - let signer = payer.pubkey(); - let bus_pda = Pubkey::find_program_address(&[BUS, &[0]], &ore::id()); - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - - // Submit tx - let ix = Instruction { - program_id: ore::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: OreInstruction::Reset.to_vec(), - }; - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_reset_shuffle_error() { - // Setup - const FUZZ: u64 = 100; - let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - - // Pdas - let signer = payer.pubkey(); - let bus_pdas = vec![ - Pubkey::find_program_address(&[BUS, &[5]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[0]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[6]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[2]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[3]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[7]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[1]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[4]], &ore::id()), - ]; - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - - // Fuzz test shuffled accounts. - // Note some shuffles may still be valid if signer and non-bus accounts are all in correct positions. - let mut rng = rand::thread_rng(); - for _ in 0..FUZZ { - let mut accounts = vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus_pdas[0].0, false), - AccountMeta::new(bus_pdas[1].0, false), - AccountMeta::new(bus_pdas[2].0, false), - AccountMeta::new(bus_pdas[3].0, false), - AccountMeta::new(bus_pdas[4].0, false), - AccountMeta::new(bus_pdas[5].0, false), - AccountMeta::new(bus_pdas[6].0, false), - AccountMeta::new(bus_pdas[7].0, false), - AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ]; - accounts.shuffle(&mut rng); - let ix = Instruction { - program_id: ore::id(), - accounts, - data: OreInstruction::Reset.to_vec(), - }; - let tx = - Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); - } -} - -enum ClockState { - Normal, - TooEarly, -} - -async fn setup_program_test_env(clock_state: ClockState) -> (BanksClient, Keypair, Keypair, Hash) { - let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); - program_test.prefer_bpf(true); - - // Busses - for i in 0..BUS_COUNT { - program_test.add_account_with_base64_data( - BUS_ADDRESSES[i], - 1057920, - ore::id(), - bs64::encode( - &[ - &(Bus::discriminator() as u64).to_le_bytes(), - Bus { - id: i as u64, - rewards: 0, - } - .to_bytes(), - ] - .concat(), - ) - .as_str(), - ); - } - - // Treasury - let admin_address = Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap(); - let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); - program_test.add_account_with_base64_data( - treasury_pda.0, - 1614720, - ore::id(), - bs64::encode( - &[ - &(Treasury::discriminator() as u64).to_le_bytes(), - Treasury { - bump: treasury_pda.1 as u64, - admin: admin_address, - difficulty: INITIAL_DIFFICULTY.into(), - last_reset_at: 0, - reward_rate: INITIAL_REWARD_RATE, - total_claimed_rewards: 0, - } - .to_bytes(), - ] - .concat(), - ) - .as_str(), - ); - - // Mint - let mut mint_src: [u8; Mint::LEN] = [0; Mint::LEN]; - Mint { - mint_authority: COption::Some(TREASURY_ADDRESS), - supply: 0, - decimals: TOKEN_DECIMALS, - is_initialized: true, - freeze_authority: COption::None, - } - .pack_into_slice(&mut mint_src); - program_test.add_account_with_base64_data( - MINT_ADDRESS, - 1461600, - spl_token::id(), - bs64::encode(&mint_src).as_str(), - ); - - // Treasury tokens - let tokens_address = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - let mut tokens_src: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; - spl_token::state::Account { - mint: MINT_ADDRESS, - owner: TREASURY_ADDRESS, - amount: 0, - delegate: COption::None, - state: AccountState::Initialized, - is_native: COption::None, - delegated_amount: 0, - close_authority: COption::None, - } - .pack_into_slice(&mut tokens_src); - program_test.add_account_with_base64_data( - tokens_address, - 2039280, - spl_token::id(), - bs64::encode(&tokens_src).as_str(), - ); - - // Set sysvar - let ts = match clock_state { - ClockState::Normal => START_AT + 1, - ClockState::TooEarly => START_AT - 1, - }; - program_test.add_sysvar_account( - sysvar::clock::id(), - &Clock { - slot: 0, - epoch_start_timestamp: 0, - epoch: 0, - leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, - unix_timestamp: ts, - }, - ); - - // Setup alt payer - let payer_alt = Keypair::new(); - program_test.add_account( - payer_alt.pubkey(), - Account { - lamports: LAMPORTS_PER_SOL, - data: vec![], - owner: system_program::id(), - executable: false, - rent_epoch: 0, - }, - ); - - let (banks, payer, blockhash) = program_test.start().await; - (banks, payer, payer_alt, blockhash) -} diff --git a/tests/test_update_admin.rs b/tests/test_update_admin.rs deleted file mode 100644 index b3bd4a7..0000000 --- a/tests/test_update_admin.rs +++ /dev/null @@ -1,128 +0,0 @@ -use ore::{state::Treasury, utils::AccountDeserialize, TREASURY_ADDRESS}; -use solana_program::{ - hash::Hash, native_token::LAMPORTS_PER_SOL, pubkey::Pubkey, rent::Rent, system_program, -}; -use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; -use solana_sdk::{ - account::Account, - signature::{Keypair, Signer}, - transaction::Transaction, -}; - -#[tokio::test] -async fn test_update_admin() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env().await; - - // Submit tx - let ix = ore::instruction::initialize(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Get treasury account - let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); - let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); - - // Submit update admin ix - let new_admin = Pubkey::new_unique(); - let ix = ore::instruction::update_admin(payer.pubkey(), new_admin); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Assert treasury state - let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); - let treasury_ = Treasury::try_from_bytes(&treasury_account.data).unwrap(); - assert_eq!(treasury_.bump, treasury.bump); - assert_eq!(treasury_.admin, new_admin); - assert_eq!(treasury_.difficulty, treasury.difficulty); - assert_eq!(treasury_.last_reset_at, treasury.last_reset_at); - assert_eq!(treasury_.reward_rate, treasury.reward_rate); - assert_eq!( - treasury_.total_claimed_rewards, - treasury.total_claimed_rewards - ); - - // Submit another update admin ix - let ix = ore::instruction::update_admin(payer.pubkey(), payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_update_admin_bad_signer() { - // Setup - let (mut banks, payer, alt_payer, blockhash) = setup_program_test_env().await; - - // Submit tx - let ix = ore::instruction::initialize(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Submit ix - let ix = ore::instruction::update_admin(alt_payer.pubkey(), Pubkey::new_unique()); - let tx = Transaction::new_signed_with_payer( - &[ix], - Some(&alt_payer.pubkey()), - &[&alt_payer], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_update_admin_not_enough_accounts() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env().await; - - // Submit tx - let ix = ore::instruction::initialize(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Submit ix without enough accounts - let mut ix = ore::instruction::update_admin(payer.pubkey(), Pubkey::new_unique()); - ix.accounts.remove(1); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { - let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); - program_test.prefer_bpf(true); - - // Setup metadata program - let data = read_file(&"tests/buffers/metadata_program.bpf"); - program_test.add_account( - mpl_token_metadata::ID, - Account { - lamports: Rent::default().minimum_balance(data.len()).max(1), - data, - owner: solana_sdk::bpf_loader::id(), - executable: true, - rent_epoch: 0, - }, - ); - - // Setup alt payer - let payer_alt = Keypair::new(); - program_test.add_account( - payer_alt.pubkey(), - Account { - lamports: LAMPORTS_PER_SOL, - data: vec![], - owner: system_program::id(), - executable: false, - rent_epoch: 0, - }, - ); - - let (banks, payer, blockhash) = program_test.start().await; - (banks, payer, payer_alt, blockhash) -} diff --git a/tests/test_update_difficulty.rs b/tests/test_update_difficulty.rs deleted file mode 100644 index 3c7d335..0000000 --- a/tests/test_update_difficulty.rs +++ /dev/null @@ -1,125 +0,0 @@ -use ore::{state::Treasury, utils::AccountDeserialize, TREASURY_ADDRESS}; -use solana_program::{ - hash::Hash, keccak::Hash as KeccakHash, native_token::LAMPORTS_PER_SOL, rent::Rent, - system_program, -}; -use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; -use solana_sdk::{ - account::Account, - signature::{Keypair, Signer}, - transaction::Transaction, -}; - -#[tokio::test] -async fn test_update_difficulty() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env().await; - - // Submit tx - let ix = ore::instruction::initialize(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Get treasury account - let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); - let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); - - // Submit update difficulty ix - let new_difficulty = KeccakHash::new_unique(); - let ix = ore::instruction::update_difficulty(payer.pubkey(), new_difficulty.into()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Assert treasury state - let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); - let treasury_ = Treasury::try_from_bytes(&treasury_account.data).unwrap(); - assert_eq!(treasury_.bump, treasury.bump); - assert_eq!(treasury_.admin, treasury.admin); - assert_eq!(treasury_.difficulty, new_difficulty.into()); - assert_eq!(treasury_.last_reset_at, treasury.last_reset_at); - assert_eq!(treasury_.reward_rate, treasury.reward_rate); - assert_eq!( - treasury_.total_claimed_rewards, - treasury.total_claimed_rewards - ); -} - -#[tokio::test] -async fn test_update_difficulty_bad_signer() { - // Setup - let (mut banks, payer, alt_payer, blockhash) = setup_program_test_env().await; - - // Submit tx - let ix = ore::instruction::initialize(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Submit update difficulty ix - let new_difficulty = KeccakHash::new_unique(); - let ix = ore::instruction::update_difficulty(alt_payer.pubkey(), new_difficulty.into()); - let tx = Transaction::new_signed_with_payer( - &[ix], - Some(&alt_payer.pubkey()), - &[&alt_payer], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -#[tokio::test] -async fn test_update_difficulty_not_enough_accounts() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env().await; - - // Submit tx - let ix = ore::instruction::initialize(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); - - // Submit ix without enough accounts - let new_difficulty = KeccakHash::new_unique(); - let mut ix = ore::instruction::update_difficulty(payer.pubkey(), new_difficulty.into()); - ix.accounts.remove(1); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} - -async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { - let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); - program_test.prefer_bpf(true); - - // Setup metadata program - let data = read_file(&"tests/buffers/metadata_program.bpf"); - program_test.add_account( - mpl_token_metadata::ID, - Account { - lamports: Rent::default().minimum_balance(data.len()).max(1), - data, - owner: solana_sdk::bpf_loader::id(), - executable: true, - rent_epoch: 0, - }, - ); - - // Setup alt payer - let payer_alt = Keypair::new(); - program_test.add_account( - payer_alt.pubkey(), - Account { - lamports: LAMPORTS_PER_SOL, - data: vec![], - owner: system_program::id(), - executable: false, - rent_epoch: 0, - }, - ); - - let (banks, payer, blockhash) = program_test.start().await; - (banks, payer, payer_alt, blockhash) -} diff --git a/utils/Cargo.toml b/utils/Cargo.toml new file mode 100644 index 0000000..cc44d0f --- /dev/null +++ b/utils/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "utils" +description = "Utils for building ORE programs" +version.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true +keywords.workspace = true + +[lib] +crate-type = ["cdylib", "lib"] +name = "utils" + +[dependencies] +bytemuck.workspace = true +solana-program.workspace = true diff --git a/src/utils.rs b/utils/src/lib.rs similarity index 82% rename from src/utils.rs rename to utils/src/lib.rs index 82445ae..1694c98 100644 --- a/src/utils.rs +++ b/utils/src/lib.rs @@ -1,4 +1,3 @@ -use num_enum::{IntoPrimitive, TryFromPrimitive}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, rent::Rent, sysvar::Sysvar, @@ -6,7 +5,7 @@ use solana_program::{ /// Creates a new pda #[inline(always)] -pub(crate) fn create_pda<'a, 'info>( +pub fn create_pda<'a, 'info>( target_account: &'a AccountInfo<'info>, owner: &Pubkey, space: usize, @@ -47,9 +46,9 @@ pub(crate) fn create_pda<'a, 'info>( rent_exempt_balance, ), &[ - payer.as_ref().clone(), - target_account.as_ref().clone(), - system_program.as_ref().clone(), + payer.clone(), + target_account.clone(), + system_program.clone(), ], )?; } @@ -57,20 +56,14 @@ pub(crate) fn create_pda<'a, 'info>( // 2) allocate space for the account solana_program::program::invoke_signed( &solana_program::system_instruction::allocate(target_account.key, space as u64), - &[ - target_account.as_ref().clone(), - system_program.as_ref().clone(), - ], + &[target_account.clone(), system_program.clone()], &[pda_seeds], )?; // 3) assign our program as the owner solana_program::program::invoke_signed( &solana_program::system_instruction::assign(target_account.key, owner), - &[ - target_account.as_ref().clone(), - system_program.as_ref().clone(), - ], + &[target_account.clone(), system_program.clone()], &[pda_seeds], )?; } @@ -78,16 +71,8 @@ pub(crate) fn create_pda<'a, 'info>( Ok(()) } -#[repr(u8)] -#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] -pub enum AccountDiscriminator { - Bus = 100, - Proof = 101, - Treasury = 102, -} - pub trait Discriminator { - fn discriminator() -> AccountDiscriminator; + fn discriminator() -> u8; //AccountDiscriminator; } pub trait AccountDeserialize { @@ -113,7 +98,7 @@ macro_rules! impl_account_from_bytes { fn try_from_bytes( data: &[u8], ) -> Result<&Self, solana_program::program_error::ProgramError> { - if (Self::discriminator() as u8).ne(&data[0]) { + if Self::discriminator().ne(&data[0]) { return Err(solana_program::program_error::ProgramError::InvalidAccountData); } bytemuck::try_from_bytes::(&data[8..]).or(Err( @@ -123,7 +108,7 @@ macro_rules! impl_account_from_bytes { fn try_from_bytes_mut( data: &mut [u8], ) -> Result<&mut Self, solana_program::program_error::ProgramError> { - if (Self::discriminator() as u8).ne(&data[0]) { + if Self::discriminator().ne(&data[0]) { return Err(solana_program::program_error::ProgramError::InvalidAccountData); } bytemuck::try_from_bytes_mut::(&mut data[8..]).or(Err(