mirror of
https://github.com/d0zingcat/ore.git
synced 2026-05-13 15:09:57 +00:00
Begin native rewrite without anchor
This commit is contained in:
18
Anchor.toml
18
Anchor.toml
@@ -1,18 +0,0 @@
|
||||
[toolchain]
|
||||
|
||||
[features]
|
||||
seeds = false
|
||||
skip-lint = false
|
||||
|
||||
[programs.localnet]
|
||||
ore = "CeJShZEAzBLwtcLQvbZc7UT38e4nUTn63Za5UFyYYDTS"
|
||||
|
||||
[registry]
|
||||
url = "https://api.apr.dev"
|
||||
|
||||
[provider]
|
||||
cluster = "Localnet"
|
||||
wallet = "/home/ubuntu/.config/solana/id.json"
|
||||
|
||||
[scripts]
|
||||
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
|
||||
339
Cargo.lock
generated
339
Cargo.lock
generated
@@ -117,167 +117,6 @@ dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-attribute-access-control"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5f619f1d04f53621925ba8a2e633ba5a6081f2ae14758cbb67f38fd823e0a3e"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-attribute-account"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7f2a3e1df4685f18d12a943a9f2a7456305401af21a07c9fe076ef9ecd6e400"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"bs58 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-attribute-constant"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9423945cb55627f0b30903288e78baf6f62c6c8ab28fb344b6b25f1ffee3dca7"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-attribute-error"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93ed12720033cc3c3bf3cfa293349c2275cd5ab99936e33dd4bf283aaad3e241"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-attribute-event"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eef4dc0371eba2d8c8b54794b0b0eb786a234a559b77593d6f80825b6d2c77a2"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-attribute-program"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b18c4f191331e078d4a6a080954d1576241c29c56638783322a18d308ab27e4f"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-derive-accounts"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de10d6e9620d3bcea56c56151cad83c5992f50d5960b3a9bebc4a50390ddc3c"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-derive-serde"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4e2e5be518ec6053d90a2a7f26843dbee607583c779e6c8395951b9739bdfbe"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"borsh-derive-internal 0.10.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-derive-space"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ecc31d19fa54840e74b7a979d44bcea49d70459de846088a1d71e87ba53c419"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-lang"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35da4785497388af0553586d55ebdc08054a8b1724720ef2749d313494f2b8ad"
|
||||
dependencies = [
|
||||
"anchor-attribute-access-control",
|
||||
"anchor-attribute-account",
|
||||
"anchor-attribute-constant",
|
||||
"anchor-attribute-error",
|
||||
"anchor-attribute-event",
|
||||
"anchor-attribute-program",
|
||||
"anchor-derive-accounts",
|
||||
"anchor-derive-serde",
|
||||
"anchor-derive-space",
|
||||
"arrayref",
|
||||
"base64 0.13.1",
|
||||
"bincode",
|
||||
"borsh 0.10.3",
|
||||
"bytemuck",
|
||||
"getrandom 0.2.11",
|
||||
"solana-program",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-spl"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c4fd6e43b2ca6220d2ef1641539e678bfc31b6cc393cf892b373b5997b6a39a"
|
||||
dependencies = [
|
||||
"anchor-lang",
|
||||
"solana-program",
|
||||
"spl-associated-token-account",
|
||||
"spl-token",
|
||||
"spl-token-2022 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-syn"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9101b84702fed2ea57bd22992f75065da5648017135b844283a2f6d74f27825"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58 0.5.0",
|
||||
"heck 0.3.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.8",
|
||||
"syn 1.0.109",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
@@ -778,15 +617,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 = "bumpalo"
|
||||
version = "3.14.0"
|
||||
@@ -805,9 +635,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.14.0"
|
||||
version = "1.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
|
||||
checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
@@ -1735,15 +1565,6 @@ version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
@@ -2420,11 +2241,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0"
|
||||
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
|
||||
dependencies = [
|
||||
"num_enum_derive 0.7.1",
|
||||
"num_enum_derive 0.7.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2441,9 +2262,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e"
|
||||
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
|
||||
dependencies = [
|
||||
"proc-macro-crate 2.0.1",
|
||||
"proc-macro2",
|
||||
@@ -2516,12 +2337,16 @@ dependencies = [
|
||||
name = "ore"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anchor-lang",
|
||||
"anchor-spl",
|
||||
"bincode",
|
||||
"bytemuck",
|
||||
"num_enum 0.7.2",
|
||||
"shank",
|
||||
"solana-program",
|
||||
"solana-program-test",
|
||||
"solana-sdk",
|
||||
"spl-associated-token-account",
|
||||
"spl-token",
|
||||
"static_assertions",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3402,6 +3227,52 @@ dependencies = [
|
||||
"keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shank"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c9395612d493b69a522725eef78a095f199d43eeb847f4a4b77ec0cacab535"
|
||||
dependencies = [
|
||||
"shank_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shank_macro"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8abef069c02e15f62233679b1e71f3152fac10f90b3ff89ebbad6a25b7497754"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"shank_macro_impl",
|
||||
"shank_render",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shank_macro_impl"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64d3d92bfcc6e08f882f2264d774d1a2f46dc36122adc1b76416ba6405a29a9c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shank_render"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a2ea9c6dd95ea311b3b81e63cf4e9c808ed04b098819e6d2c4b1a467d587203"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"shank_macro_impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
@@ -3482,7 +3353,7 @@ dependencies = [
|
||||
"Inflector",
|
||||
"base64 0.21.5",
|
||||
"bincode",
|
||||
"bs58 0.4.0",
|
||||
"bs58",
|
||||
"bv",
|
||||
"lazy_static",
|
||||
"serde",
|
||||
@@ -3491,7 +3362,7 @@ dependencies = [
|
||||
"solana-config-program",
|
||||
"solana-sdk",
|
||||
"spl-token",
|
||||
"spl-token-2022 1.0.0",
|
||||
"spl-token-2022",
|
||||
"spl-token-group-interface",
|
||||
"spl-token-metadata-interface",
|
||||
"thiserror",
|
||||
@@ -3792,7 +3663,7 @@ dependencies = [
|
||||
"ahash 0.8.6",
|
||||
"blake3",
|
||||
"block-buffer 0.10.4",
|
||||
"bs58 0.4.0",
|
||||
"bs58",
|
||||
"bv",
|
||||
"byteorder",
|
||||
"cc",
|
||||
@@ -3941,7 +3812,7 @@ dependencies = [
|
||||
"blake3",
|
||||
"borsh 0.10.3",
|
||||
"borsh 0.9.3",
|
||||
"bs58 0.4.0",
|
||||
"bs58",
|
||||
"bv",
|
||||
"bytemuck",
|
||||
"cc",
|
||||
@@ -4127,7 +3998,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.21.5",
|
||||
"bincode",
|
||||
"bs58 0.4.0",
|
||||
"bs58",
|
||||
"indicatif",
|
||||
"log",
|
||||
"reqwest",
|
||||
@@ -4151,7 +4022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "131662e5eea4fa5fc88b01f07d9e430315c0976be848ba3994244249c5fb033a"
|
||||
dependencies = [
|
||||
"base64 0.21.5",
|
||||
"bs58 0.4.0",
|
||||
"bs58",
|
||||
"jsonrpc-core",
|
||||
"reqwest",
|
||||
"semver",
|
||||
@@ -4162,7 +4033,7 @@ dependencies = [
|
||||
"solana-sdk",
|
||||
"solana-transaction-status",
|
||||
"solana-version",
|
||||
"spl-token-2022 1.0.0",
|
||||
"spl-token-2022",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -4267,7 +4138,7 @@ dependencies = [
|
||||
"bincode",
|
||||
"bitflags 2.4.1",
|
||||
"borsh 0.10.3",
|
||||
"bs58 0.4.0",
|
||||
"bs58",
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
@@ -4316,7 +4187,7 @@ version = "1.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60f58786e949f43b8c9b826fdfa5ad8586634b077ab04f989fb8e30535786712"
|
||||
dependencies = [
|
||||
"bs58 0.4.0",
|
||||
"bs58",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
@@ -4455,7 +4326,7 @@ dependencies = [
|
||||
"base64 0.21.5",
|
||||
"bincode",
|
||||
"borsh 0.10.3",
|
||||
"bs58 0.4.0",
|
||||
"bs58",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"serde",
|
||||
@@ -4466,7 +4337,7 @@ dependencies = [
|
||||
"spl-associated-token-account",
|
||||
"spl-memo",
|
||||
"spl-token",
|
||||
"spl-token-2022 1.0.0",
|
||||
"spl-token-2022",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -4638,7 +4509,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"solana-program",
|
||||
"spl-token",
|
||||
"spl-token-2022 1.0.0",
|
||||
"spl-token-2022",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -4724,20 +4595,6 @@ dependencies = [
|
||||
"syn 2.0.43",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spl-tlv-account-resolution"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"solana-program",
|
||||
"spl-discriminator",
|
||||
"spl-pod",
|
||||
"spl-program-error",
|
||||
"spl-type-length-value",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spl-tlv-account-resolution"
|
||||
version = "0.5.0"
|
||||
@@ -4767,28 +4624,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spl-token-2022"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"bytemuck",
|
||||
"num-derive 0.4.1",
|
||||
"num-traits",
|
||||
"num_enum 0.7.1",
|
||||
"solana-program",
|
||||
"solana-zk-token-sdk",
|
||||
"spl-memo",
|
||||
"spl-pod",
|
||||
"spl-token",
|
||||
"spl-token-metadata-interface",
|
||||
"spl-transfer-hook-interface 0.3.0",
|
||||
"spl-type-length-value",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spl-token-2022"
|
||||
version = "1.0.0"
|
||||
@@ -4799,7 +4634,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"num-derive 0.4.1",
|
||||
"num-traits",
|
||||
"num_enum 0.7.1",
|
||||
"num_enum 0.7.2",
|
||||
"solana-program",
|
||||
"solana-security-txt",
|
||||
"solana-zk-token-sdk",
|
||||
@@ -4808,7 +4643,7 @@ dependencies = [
|
||||
"spl-token",
|
||||
"spl-token-group-interface",
|
||||
"spl-token-metadata-interface",
|
||||
"spl-transfer-hook-interface 0.4.1",
|
||||
"spl-transfer-hook-interface",
|
||||
"spl-type-length-value",
|
||||
"thiserror",
|
||||
]
|
||||
@@ -4840,22 +4675,6 @@ dependencies = [
|
||||
"spl-type-length-value",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spl-transfer-hook-interface"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"bytemuck",
|
||||
"solana-program",
|
||||
"spl-discriminator",
|
||||
"spl-pod",
|
||||
"spl-program-error",
|
||||
"spl-tlv-account-resolution 0.4.0",
|
||||
"spl-type-length-value",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spl-transfer-hook-interface"
|
||||
version = "0.4.1"
|
||||
@@ -4868,7 +4687,7 @@ dependencies = [
|
||||
"spl-discriminator",
|
||||
"spl-pod",
|
||||
"spl-program-error",
|
||||
"spl-tlv-account-resolution 0.5.0",
|
||||
"spl-tlv-account-resolution",
|
||||
"spl-type-length-value",
|
||||
]
|
||||
|
||||
@@ -4918,7 +4737,7 @@ version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
@@ -5466,12 +5285,6 @@ dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.11"
|
||||
|
||||
44
Cargo.toml
44
Cargo.toml
@@ -1,17 +1,33 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
||||
[package]
|
||||
name = "ore"
|
||||
version = "0.1.0"
|
||||
description = "Ore is a cryptocurrency everyone can mine"
|
||||
edition = "2021"
|
||||
license = "Apache 2.0"
|
||||
homepage = ""
|
||||
repository = ""
|
||||
documentation = ""
|
||||
readme = "./README.md"
|
||||
keywords = ["solana"]
|
||||
|
||||
# [package.metadata.kani.flags]
|
||||
# default-unwind = 1
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "ore"
|
||||
|
||||
[profile.release]
|
||||
overflow-checks = true
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
default = []
|
||||
|
||||
[profile.release.build-override]
|
||||
opt-level = 3
|
||||
incremental = false
|
||||
codegen-units = 1
|
||||
[dependencies]
|
||||
bytemuck = "1.14.3"
|
||||
num_enum = "0.7.2"
|
||||
shank = "0.3.0"
|
||||
solana-program = "^1.16"
|
||||
spl-token = { version = "^4", features = ["no-entrypoint"] }
|
||||
spl-associated-token-account = { version = "^2.2", features = [ "no-entrypoint" ] }
|
||||
static_assertions = "1.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-program-test = "^1.16"
|
||||
solana-sdk = "^1.16"
|
||||
tokio = { version = "1.35", features = ["full"] }
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
[package]
|
||||
name = "ore"
|
||||
version = "0.1.0"
|
||||
description = "Ore is a cryptocurrency everyone can mine"
|
||||
edition = "2021"
|
||||
license = "Apache 2.0"
|
||||
homepage = ""
|
||||
repository = ""
|
||||
documentation = ""
|
||||
readme = "./README.md"
|
||||
keywords = ["solana"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "ore"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = "0.29.0"
|
||||
anchor-spl = { version = "0.29.0", features = ["token"] }
|
||||
bincode = "1.3.3"
|
||||
solana-program = "^1.16"
|
||||
static_assertions = "1.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-program-test = "^1.16"
|
||||
@@ -1,2 +0,0 @@
|
||||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
||||
@@ -1,795 +0,0 @@
|
||||
use std::mem::size_of;
|
||||
|
||||
use anchor_lang::{
|
||||
prelude::*,
|
||||
solana_program::{
|
||||
keccak::{hashv, Hash},
|
||||
pubkey,
|
||||
slot_hashes::SlotHash,
|
||||
system_program, sysvar,
|
||||
},
|
||||
};
|
||||
use anchor_spl::{
|
||||
associated_token,
|
||||
token::{self, Mint, MintTo, TokenAccount},
|
||||
};
|
||||
|
||||
// TODO Test admin and difficulty adjustment functions!
|
||||
|
||||
declare_id!("CeJShZEAzBLwtcLQvbZc7UT38e4nUTn63Za5UFyYYDTS");
|
||||
|
||||
// TODO Set this before deployment
|
||||
/// The unix timestamp after which mining is allowed.
|
||||
pub const START_AT: i64 = 0;
|
||||
|
||||
/// The initial reward rate to payout in the first epoch.
|
||||
pub const INITIAL_REWARD_RATE: u64 = 10u64.pow(3u32);
|
||||
|
||||
/// The initial hashing difficulty. The admin authority can update this in the future, if needed.
|
||||
pub const INITIAL_DIFFICULTY: Hash = Hash::new_from_array([
|
||||
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, 255,
|
||||
]);
|
||||
|
||||
/// The mint address of the ORE token.
|
||||
pub const TOKEN_MINT_ADDRESS: Pubkey = pubkey!("37TDfMS8NHpyhyCXBrY9m7rRrtj1f7TrFzD1iXqmTeUX");
|
||||
|
||||
/// 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: u8 = 8;
|
||||
|
||||
/// The smoothing factor for reward rate changes. The reward rate cannot change by more or less
|
||||
/// than 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
|
||||
);
|
||||
|
||||
#[program]
|
||||
mod ore {
|
||||
use super::*;
|
||||
|
||||
/// Initializes the treasury account. Can only be invoked once.
|
||||
pub fn initialize_treasury(ctx: Context<InitializeTreasury>) -> Result<()> {
|
||||
ctx.accounts.treasury.bump = ctx.bumps.treasury;
|
||||
ctx.accounts.treasury.admin = ctx.accounts.signer.key();
|
||||
ctx.accounts.treasury.difficulty = INITIAL_DIFFICULTY;
|
||||
ctx.accounts.treasury.reward_rate = INITIAL_REWARD_RATE;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Initializes the bus accounts. Can only be invoked once.
|
||||
pub fn initialize_busses(ctx: Context<InitializeBusses>) -> Result<()> {
|
||||
ctx.accounts.bus_0.bump = ctx.bumps.bus_0;
|
||||
ctx.accounts.bus_0.id = 0;
|
||||
ctx.accounts.bus_1.bump = ctx.bumps.bus_1;
|
||||
ctx.accounts.bus_1.id = 1;
|
||||
ctx.accounts.bus_2.bump = ctx.bumps.bus_2;
|
||||
ctx.accounts.bus_2.id = 2;
|
||||
ctx.accounts.bus_3.bump = ctx.bumps.bus_3;
|
||||
ctx.accounts.bus_3.id = 3;
|
||||
ctx.accounts.bus_4.bump = ctx.bumps.bus_4;
|
||||
ctx.accounts.bus_4.id = 4;
|
||||
ctx.accounts.bus_5.bump = ctx.bumps.bus_5;
|
||||
ctx.accounts.bus_5.id = 5;
|
||||
ctx.accounts.bus_6.bump = ctx.bumps.bus_6;
|
||||
ctx.accounts.bus_6.id = 6;
|
||||
ctx.accounts.bus_7.bump = ctx.bumps.bus_7;
|
||||
ctx.accounts.bus_7.id = 7;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Initializes a proof account for a new miner. Can only invoked once per signer.
|
||||
pub fn initialize_proof(ctx: Context<InitializeProof>) -> Result<()> {
|
||||
ctx.accounts.proof.authority = ctx.accounts.signer.key();
|
||||
ctx.accounts.proof.bump = ctx.bumps.proof;
|
||||
ctx.accounts.proof.hash = hashv(&[&ctx.accounts.signer.key().to_bytes()]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the reward rate and starts the new epoch.
|
||||
pub fn reset_epoch(ctx: Context<ResetEpoch>) -> Result<()> {
|
||||
// Validate epoch has ended.
|
||||
let clock = Clock::get().unwrap();
|
||||
let treasury = &mut ctx.accounts.treasury;
|
||||
let epoch_end_at = treasury.epoch_start_at.saturating_add(EPOCH_DURATION);
|
||||
require!(
|
||||
clock.unix_timestamp.ge(&epoch_end_at),
|
||||
ProgramError::ClockInvalid
|
||||
);
|
||||
|
||||
// Calculate total rewards issued during the epoch.
|
||||
let bus_0 = &mut ctx.accounts.bus_0;
|
||||
let bus_1 = &mut ctx.accounts.bus_1;
|
||||
let bus_2 = &mut ctx.accounts.bus_2;
|
||||
let bus_3 = &mut ctx.accounts.bus_3;
|
||||
let bus_4 = &mut ctx.accounts.bus_4;
|
||||
let bus_5 = &mut ctx.accounts.bus_5;
|
||||
let bus_6 = &mut ctx.accounts.bus_6;
|
||||
let bus_7 = &mut ctx.accounts.bus_7;
|
||||
let total_available_rewards = bus_0
|
||||
.available_rewards
|
||||
.saturating_add(bus_1.available_rewards)
|
||||
.saturating_add(bus_2.available_rewards)
|
||||
.saturating_add(bus_3.available_rewards)
|
||||
.saturating_add(bus_4.available_rewards)
|
||||
.saturating_add(bus_5.available_rewards)
|
||||
.saturating_add(bus_6.available_rewards)
|
||||
.saturating_add(bus_7.available_rewards);
|
||||
let total_epoch_rewards = MAX_EPOCH_REWARDS.saturating_sub(total_available_rewards);
|
||||
|
||||
// Update the reward amount for the next epoch.
|
||||
treasury.reward_rate = calculate_new_reward_rate(treasury.reward_rate, total_epoch_rewards);
|
||||
treasury.epoch_start_at = clock.unix_timestamp;
|
||||
|
||||
// Reset bus accounts.
|
||||
bus_0.available_rewards = BUS_EPOCH_REWARDS;
|
||||
bus_1.available_rewards = BUS_EPOCH_REWARDS;
|
||||
bus_2.available_rewards = BUS_EPOCH_REWARDS;
|
||||
bus_3.available_rewards = BUS_EPOCH_REWARDS;
|
||||
bus_4.available_rewards = BUS_EPOCH_REWARDS;
|
||||
bus_5.available_rewards = BUS_EPOCH_REWARDS;
|
||||
bus_6.available_rewards = BUS_EPOCH_REWARDS;
|
||||
bus_7.available_rewards = BUS_EPOCH_REWARDS;
|
||||
|
||||
// Top up treasury token account.
|
||||
token::mint_to(
|
||||
CpiContext::new_with_signer(
|
||||
ctx.accounts.token_program.to_account_info(),
|
||||
MintTo {
|
||||
authority: treasury.to_account_info(),
|
||||
mint: ctx.accounts.mint.to_account_info(),
|
||||
to: ctx.accounts.treasury_tokens.to_account_info(),
|
||||
},
|
||||
&[&[TREASURY, &[treasury.bump]]],
|
||||
),
|
||||
total_epoch_rewards,
|
||||
)?;
|
||||
|
||||
// Log data.
|
||||
msg!("Epoch rewards: {:?}", total_epoch_rewards);
|
||||
msg!("Reward rate: {:?}", treasury.reward_rate);
|
||||
msg!("Supply: {:?}", ctx.accounts.mint.supply);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Distributes Ore tokens to the signer if a valid hash is provided.
|
||||
pub fn mine(ctx: Context<Mine>, hash: Hash, nonce: u64) -> Result<()> {
|
||||
// Validate epoch is active.
|
||||
let clock = Clock::get().unwrap();
|
||||
let treasury = &mut ctx.accounts.treasury;
|
||||
let epoch_end_at = treasury.epoch_start_at.saturating_add(EPOCH_DURATION);
|
||||
require!(
|
||||
clock.unix_timestamp.lt(&epoch_end_at),
|
||||
ProgramError::EpochNeedsReset
|
||||
);
|
||||
|
||||
// Validate provided hash.
|
||||
let proof = &mut ctx.accounts.proof;
|
||||
validate_hash(
|
||||
proof.hash.clone(),
|
||||
hash.clone(),
|
||||
ctx.accounts.signer.key(),
|
||||
nonce,
|
||||
treasury.difficulty,
|
||||
)?;
|
||||
|
||||
// Update claimable rewards.
|
||||
let bus = &mut ctx.accounts.bus;
|
||||
require!(
|
||||
bus.available_rewards.ge(&treasury.reward_rate),
|
||||
ProgramError::BusInsufficientFunds
|
||||
);
|
||||
bus.available_rewards = bus.available_rewards.saturating_sub(treasury.reward_rate);
|
||||
proof.claimable_rewards = proof.claimable_rewards.saturating_add(treasury.reward_rate);
|
||||
|
||||
// Hash most recent slot hash into the next challenge to prevent pre-mining attacks.
|
||||
let slot_hash_bytes = &ctx.accounts.slot_hashes.data.borrow()[0..size_of::<SlotHash>()];
|
||||
let slot_hash: SlotHash = bincode::deserialize(slot_hash_bytes).unwrap();
|
||||
proof.hash = hashv(&[hash.as_ref(), slot_hash.1.as_ref()]);
|
||||
|
||||
// Update lifetime stats.
|
||||
proof.total_hashes = proof.total_hashes.saturating_add(1);
|
||||
proof.total_rewards = proof.total_rewards.saturating_add(1);
|
||||
|
||||
// Log data.
|
||||
msg!("Reward rate: {:?}", treasury.reward_rate);
|
||||
msg!("Claimable rewards: {:?}", proof.claimable_rewards);
|
||||
msg!("Total hashes: {:?}", proof.total_hashes);
|
||||
msg!("Total rewards: {:?}", proof.total_rewards);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn claim(ctx: Context<Claim>, amount: u64) -> Result<()> {
|
||||
// Validate claim is for an appropriate quantity of tokens.
|
||||
let proof = &mut ctx.accounts.proof;
|
||||
require!(
|
||||
proof.claimable_rewards.ge(&amount),
|
||||
ProgramError::ClaimTooLarge
|
||||
);
|
||||
|
||||
// Update claimable amount.
|
||||
proof.claimable_rewards = proof.claimable_rewards.saturating_sub(amount);
|
||||
|
||||
// Update lifetime status.
|
||||
let treasury = &mut ctx.accounts.treasury;
|
||||
treasury.total_claimed_rewards = treasury.total_claimed_rewards.saturating_add(amount);
|
||||
|
||||
// Distribute tokens from treasury to beneficiary.
|
||||
let treasury_tokens = &ctx.accounts.treasury_tokens;
|
||||
require!(
|
||||
treasury_tokens.amount.ge(&amount),
|
||||
ProgramError::TreasuryInsufficientFunds
|
||||
);
|
||||
token::transfer(
|
||||
CpiContext::new_with_signer(
|
||||
ctx.accounts.token_program.to_account_info(),
|
||||
token::Transfer {
|
||||
from: treasury_tokens.to_account_info(),
|
||||
to: ctx.accounts.beneficiary.to_account_info(),
|
||||
authority: treasury.to_account_info(),
|
||||
},
|
||||
&[&[TREASURY, &[treasury.bump]]],
|
||||
),
|
||||
amount,
|
||||
)?;
|
||||
|
||||
// Log data.
|
||||
msg!("Claimable rewards: {:?}", proof.claimable_rewards);
|
||||
msg!(
|
||||
"Total claimed rewards: {:?}",
|
||||
treasury.total_claimed_rewards
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the admin to a new value. Can only be invoked by the admin authority.
|
||||
pub fn update_admin(ctx: Context<UpdateDifficulty>, new_admin: Pubkey) -> Result<()> {
|
||||
ctx.accounts.treasury.admin = new_admin;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the difficulty to a new value. Can only be invoked by the admin authority.
|
||||
///
|
||||
/// Ore subdivides into 1B units of indivisible nanoORE. If global hashpower increases to the
|
||||
/// point where >1B valid hashes are being submitted per epoch, the Ore inflation rate could
|
||||
/// be pushed steadily above 1 ORE/epoch. The protocol guarantees inflation can never exceed
|
||||
/// 2 ORE/epoch, but it is the responsibility of the admin to adjust the mining difficulty
|
||||
/// as needed to maintain the 1 ORE/epoch average.
|
||||
///
|
||||
/// It is worth noting that Solana today processes well below 1M real TPS or
|
||||
/// (60 * 1,000,000) = 60,000,000 hashes per epoch. Even if every transaction on the network
|
||||
/// were mine operation, this is still two orders of magnitude below the threshold where the
|
||||
/// Ore inflation rate would be challenged. So in practice, Solana is likely to reach its
|
||||
/// network saturation point long before the Ore inflation hits its boundary condition.
|
||||
pub fn update_difficulty(ctx: Context<UpdateDifficulty>, new_difficulty: Hash) -> Result<()> {
|
||||
ctx.accounts.treasury.difficulty = new_difficulty;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_hash(
|
||||
current_hash: Hash,
|
||||
hash: Hash,
|
||||
signer: Pubkey,
|
||||
nonce: u64,
|
||||
difficulty: Hash,
|
||||
) -> Result<()> {
|
||||
// Validate hash correctness.
|
||||
let hash_ = hashv(&[
|
||||
current_hash.as_ref(),
|
||||
signer.as_ref(),
|
||||
nonce.to_be_bytes().as_slice(),
|
||||
]);
|
||||
require!(hash.eq(&hash_), ProgramError::HashInvalid);
|
||||
|
||||
// Validate hash difficulty.
|
||||
require!(hash.le(&difficulty), ProgramError::HashInvalid);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) -> u64 {
|
||||
// Avoid division by zero. Leave the reward rate unchanged.
|
||||
if epoch_rewards.eq(&0) {
|
||||
return current_rate;
|
||||
}
|
||||
|
||||
// Calculate new reward rate.
|
||||
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 to not change by more than a constant factor from one epoch to the next.
|
||||
let new_rate_min = current_rate.saturating_div(SMOOTHING_FACTOR);
|
||||
let new_rate_max = current_rate.saturating_mul(SMOOTHING_FACTOR);
|
||||
let new_rate_smoothed = new_rate_min.max(new_rate_max.min(new_rate));
|
||||
|
||||
// Prevent reward rate from dropping below 1 or exceeding BUS_EPOCH_REWARDS and return.
|
||||
new_rate_smoothed.max(1).min(BUS_EPOCH_REWARDS)
|
||||
}
|
||||
|
||||
/// The seed of the bus account PDA.
|
||||
pub const BUS: &[u8] = b"bus";
|
||||
|
||||
/// The seed of the proof account PDA.
|
||||
pub const PROOF: &[u8] = b"proof";
|
||||
|
||||
/// The seed of the treasury account PDA.
|
||||
pub const TREASURY: &[u8] = b"treasury";
|
||||
|
||||
/// Bus is an account type used to track the number of processed hashes and issued rewards
|
||||
/// during an epoch. There are 8 bus accounts to provide sufficient parallelism for mine ops
|
||||
/// and reduce write lock contention.
|
||||
#[account]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Bus {
|
||||
/// The bump of the bus account PDA.
|
||||
pub bump: u8,
|
||||
|
||||
/// The ID of the bus account.
|
||||
pub id: u8,
|
||||
|
||||
/// The quantity of rewards this bus can issue in the current epoch epoch.
|
||||
pub available_rewards: u64,
|
||||
}
|
||||
|
||||
/// Proof is an account type used to track a miner's hash chain.
|
||||
#[account]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Proof {
|
||||
/// The bump of the proof account PDA.
|
||||
pub bump: u8,
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// Treasury is an account type used to track global program variables.
|
||||
#[account]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Treasury {
|
||||
/// The bump of the treasury account PDA.
|
||||
pub bump: u8,
|
||||
|
||||
/// The admin authority with permission to update the difficulty.
|
||||
pub admin: Pubkey,
|
||||
|
||||
/// The hash difficulty.
|
||||
pub difficulty: Hash,
|
||||
|
||||
/// The timestamp of the start of the current epoch.
|
||||
pub epoch_start_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,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct InitializeTreasury<'info> {
|
||||
/// The signer of the transaction.
|
||||
#[account(mut)]
|
||||
pub signer: Signer<'info>,
|
||||
|
||||
/// The Ore token mint.
|
||||
#[account(init, address = TOKEN_MINT_ADDRESS, payer = signer, mint::decimals = TOKEN_DECIMALS, mint::authority = treasury)]
|
||||
pub mint: Account<'info, Mint>,
|
||||
|
||||
/// The treasury account.
|
||||
#[account(init, seeds = [TREASURY], bump, payer = signer, space = 8 + size_of::<Treasury>())]
|
||||
pub treasury: Account<'info, Treasury>,
|
||||
|
||||
/// The treasury token account.
|
||||
#[account(init, associated_token::mint = mint, associated_token::authority = treasury, payer = signer)]
|
||||
pub treasury_tokens: Account<'info, TokenAccount>,
|
||||
|
||||
/// The Solana system program.
|
||||
#[account(address = system_program::ID)]
|
||||
pub system_program: Program<'info, System>,
|
||||
|
||||
/// The SPL token program.
|
||||
#[account(address = anchor_spl::token::ID)]
|
||||
pub token_program: Program<'info, token::Token>,
|
||||
|
||||
/// The SPL associated token program.
|
||||
#[account(address = anchor_spl::associated_token::ID)]
|
||||
pub associated_token_program: Program<'info, associated_token::AssociatedToken>,
|
||||
|
||||
/// The rent sysvar account.
|
||||
#[account(address = sysvar::rent::ID)]
|
||||
pub rent: Sysvar<'info, Rent>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct InitializeBusses<'info> {
|
||||
/// The signer of the transaction.
|
||||
#[account(mut)]
|
||||
pub signer: Signer<'info>,
|
||||
|
||||
/// The treasury account.
|
||||
#[account(seeds = [TREASURY], bump = treasury.bump)]
|
||||
pub treasury: Account<'info, Treasury>,
|
||||
|
||||
/// The Ore token mint account.
|
||||
#[account(address = TOKEN_MINT_ADDRESS)]
|
||||
pub mint: Account<'info, Mint>,
|
||||
|
||||
/// Bus account 0.
|
||||
#[account(init, seeds = [BUS, &[0]], bump, payer = signer, space = 8 + size_of::<Bus>())]
|
||||
pub bus_0: Account<'info, Bus>,
|
||||
|
||||
/// Bus account 1.
|
||||
#[account(init, seeds = [BUS, &[1]], bump, payer = signer, space = 8 + size_of::<Bus>())]
|
||||
pub bus_1: Account<'info, Bus>,
|
||||
|
||||
/// Bus account 2.
|
||||
#[account(init, seeds = [BUS, &[2]], bump, payer = signer, space = 8 + size_of::<Bus>())]
|
||||
pub bus_2: Account<'info, Bus>,
|
||||
|
||||
/// Bus account 3.
|
||||
#[account(init, seeds = [BUS, &[3]], bump, payer = signer, space = 8 + size_of::<Bus>())]
|
||||
pub bus_3: Account<'info, Bus>,
|
||||
|
||||
/// Bus account 4.
|
||||
#[account(init, seeds = [BUS, &[4]], bump, payer = signer, space = 8 + size_of::<Bus>())]
|
||||
pub bus_4: Account<'info, Bus>,
|
||||
|
||||
/// Bus account 5.
|
||||
#[account(init, seeds = [BUS, &[5]], bump, payer = signer, space = 8 + size_of::<Bus>())]
|
||||
pub bus_5: Account<'info, Bus>,
|
||||
|
||||
/// Bus account 6.
|
||||
#[account(init, seeds = [BUS, &[6]], bump, payer = signer, space = 8 + size_of::<Bus>())]
|
||||
pub bus_6: Account<'info, Bus>,
|
||||
|
||||
/// Bus account 7.
|
||||
#[account(init, seeds = [BUS, &[7]], bump, payer = signer, space = 8 + size_of::<Bus>())]
|
||||
pub bus_7: Account<'info, Bus>,
|
||||
|
||||
/// The Solana system program.
|
||||
#[account(address = system_program::ID)]
|
||||
pub system_program: Program<'info, System>,
|
||||
}
|
||||
|
||||
/// InitializeProof initializes a new proof account for a miner.
|
||||
#[derive(Accounts)]
|
||||
pub struct InitializeProof<'info> {
|
||||
/// The signer of the transaction.
|
||||
#[account(mut)]
|
||||
pub signer: Signer<'info>,
|
||||
|
||||
/// The proof account.
|
||||
#[account(init, seeds = [PROOF, signer.key().as_ref()], bump, payer = signer, space = 8 + size_of::<Proof>())]
|
||||
pub proof: Account<'info, Proof>,
|
||||
|
||||
/// The Solana system program.
|
||||
#[account(address = system_program::ID)]
|
||||
pub system_program: Program<'info, System>,
|
||||
}
|
||||
|
||||
// ResetEpoch adjusts the reward rate based on global hashpower and begins the new epoch.
|
||||
#[derive(Accounts)]
|
||||
pub struct ResetEpoch<'info> {
|
||||
/// The signer of the transaction.
|
||||
#[account(mut)]
|
||||
pub signer: Signer<'info>,
|
||||
|
||||
/// Bus account 0.
|
||||
#[account(mut, seeds = [BUS, &[0]], bump = bus_0.bump)]
|
||||
pub bus_0: Box<Account<'info, Bus>>,
|
||||
|
||||
/// Bus account 1.
|
||||
#[account(mut, seeds = [BUS, &[1]], bump = bus_1.bump)]
|
||||
pub bus_1: Box<Account<'info, Bus>>,
|
||||
|
||||
/// Bus account 2.
|
||||
#[account(mut, seeds = [BUS, &[2]], bump = bus_2.bump)]
|
||||
pub bus_2: Box<Account<'info, Bus>>,
|
||||
|
||||
/// Bus account 3.
|
||||
#[account(mut, seeds = [BUS, &[3]], bump = bus_3.bump)]
|
||||
pub bus_3: Box<Account<'info, Bus>>,
|
||||
|
||||
/// Bus account 4.
|
||||
#[account(mut, seeds = [BUS, &[4]], bump = bus_4.bump)]
|
||||
pub bus_4: Box<Account<'info, Bus>>,
|
||||
|
||||
/// Bus account 5.
|
||||
#[account(mut, seeds = [BUS, &[5]], bump = bus_5.bump)]
|
||||
pub bus_5: Box<Account<'info, Bus>>,
|
||||
|
||||
/// Bus account 6.
|
||||
#[account(mut, seeds = [BUS, &[6]], bump = bus_6.bump)]
|
||||
pub bus_6: Box<Account<'info, Bus>>,
|
||||
|
||||
/// Bus account 7.
|
||||
#[account(mut, seeds = [BUS, &[7]], bump = bus_7.bump)]
|
||||
pub bus_7: Box<Account<'info, Bus>>,
|
||||
|
||||
/// The Ore token mint account.
|
||||
#[account(mut, address = TOKEN_MINT_ADDRESS)]
|
||||
pub mint: Account<'info, Mint>,
|
||||
|
||||
/// The treasury account.
|
||||
#[account(mut, seeds = [TREASURY], bump = treasury.bump)]
|
||||
pub treasury: Account<'info, Treasury>,
|
||||
|
||||
/// The treasury token account.
|
||||
#[account(mut, associated_token::mint = mint, associated_token::authority = treasury)]
|
||||
pub treasury_tokens: Account<'info, TokenAccount>,
|
||||
|
||||
/// The SPL token program.
|
||||
#[account(address = anchor_spl::token::ID)]
|
||||
pub token_program: Program<'info, token::Token>,
|
||||
}
|
||||
|
||||
/// Mine distributes Ore to the beneficiary if the signer provides a valid hash.
|
||||
#[derive(Accounts)]
|
||||
#[instruction(hash: Hash, nonce: u64)]
|
||||
pub struct Mine<'info> {
|
||||
/// The signer of the transaction (i.e. the miner).
|
||||
#[account(mut, address = proof.authority)]
|
||||
pub signer: Signer<'info>,
|
||||
|
||||
/// A bus account.
|
||||
#[account(mut, constraint = bus.id.lt(&BUS_COUNT) @ ProgramError::BusInvalid)]
|
||||
pub bus: Account<'info, Bus>,
|
||||
|
||||
/// The proof account.
|
||||
#[account(mut, seeds = [PROOF, signer.key().as_ref()], bump = proof.bump)]
|
||||
pub proof: Account<'info, Proof>,
|
||||
|
||||
/// The treasury account.
|
||||
#[account(seeds = [TREASURY], bump = treasury.bump)]
|
||||
pub treasury: Account<'info, Treasury>,
|
||||
|
||||
/// The SPL token program.
|
||||
#[account(address = anchor_spl::token::ID)]
|
||||
pub token_program: Program<'info, token::Token>,
|
||||
|
||||
/// The slot hashes sysvar account.
|
||||
/// CHECK: SlotHashes is too large to deserialize. Instead we manually verify the sysvar address and deserialize only the slice we need.
|
||||
#[account(address = sysvar::slot_hashes::ID)]
|
||||
pub slot_hashes: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(amount: u64)]
|
||||
pub struct Claim<'info> {
|
||||
/// The signer of the transaction (i.e. the miner).
|
||||
#[account(mut, address = proof.authority)]
|
||||
pub signer: Signer<'info>,
|
||||
|
||||
/// The beneficiary token account to distribute rewards to.
|
||||
#[account(mut, token::mint = mint)]
|
||||
pub beneficiary: Account<'info, TokenAccount>,
|
||||
|
||||
/// The Ore token mint account.
|
||||
#[account(address = TOKEN_MINT_ADDRESS)]
|
||||
pub mint: Account<'info, Mint>,
|
||||
|
||||
/// The proof account.
|
||||
#[account(mut, seeds = [PROOF, signer.key().as_ref()], bump = proof.bump)]
|
||||
pub proof: Account<'info, Proof>,
|
||||
|
||||
/// The treasury account.
|
||||
#[account(mut, seeds = [TREASURY], bump = treasury.bump)]
|
||||
pub treasury: Account<'info, Treasury>,
|
||||
|
||||
/// The treasury token account.
|
||||
#[account(mut, associated_token::mint = mint, associated_token::authority = treasury)]
|
||||
pub treasury_tokens: Account<'info, TokenAccount>,
|
||||
|
||||
/// The SPL token program.
|
||||
#[account(address = anchor_spl::token::ID)]
|
||||
pub token_program: Program<'info, token::Token>,
|
||||
}
|
||||
|
||||
/// UpdateAdmin allows the admin to reassign the admin authority.
|
||||
#[derive(Accounts)]
|
||||
#[instruction(new_admin: Pubkey)]
|
||||
pub struct UpdateAdmin<'info> {
|
||||
/// The signer of the transaction (i.e. the admin).
|
||||
#[account(mut)]
|
||||
pub signer: Signer<'info>,
|
||||
|
||||
/// The treasury account.
|
||||
#[account(mut, seeds = [TREASURY], bump = treasury.bump, constraint = treasury.admin.eq(&signer.key()) @ ProgramError::NotAuthorized)]
|
||||
pub treasury: Account<'info, Treasury>,
|
||||
}
|
||||
|
||||
/// UpdateDifficulty allows the admin to update the mining difficulty.
|
||||
#[derive(Accounts)]
|
||||
#[instruction(new_difficulty: Hash)]
|
||||
pub struct UpdateDifficulty<'info> {
|
||||
/// The signer of the transaction (i.e. the admin).
|
||||
#[account(mut)]
|
||||
pub signer: Signer<'info>,
|
||||
|
||||
/// The treasury account.
|
||||
#[account(mut, seeds = [TREASURY], bump = treasury.bump, constraint = treasury.admin.eq(&signer.key()) @ ProgramError::NotAuthorized)]
|
||||
pub treasury: Account<'info, Treasury>,
|
||||
}
|
||||
|
||||
/// MineEvent logs revelant data about a successful Ore mining transaction.
|
||||
#[event]
|
||||
#[derive(Debug)]
|
||||
pub struct MineEvent {
|
||||
/// The signer of the transaction (i.e. the miner).
|
||||
pub signer: Pubkey,
|
||||
|
||||
/// The beneficiary token account to which rewards were minted.
|
||||
pub beneficiary: Pubkey,
|
||||
|
||||
/// The quantity of new Ore tokens that were mined.
|
||||
pub reward: u64,
|
||||
|
||||
/// The current Ore token supply.
|
||||
pub supply: u64,
|
||||
|
||||
/// The current mining difficulty.
|
||||
pub difficulty: Hash,
|
||||
|
||||
/// The valid hash provided by the signer.
|
||||
pub hash: Hash,
|
||||
|
||||
/// The nonce provided by the signer.
|
||||
pub nonce: u64,
|
||||
}
|
||||
|
||||
#[error_code]
|
||||
pub enum ProgramError {
|
||||
#[msg("The clock time is invalid")]
|
||||
ClockInvalid,
|
||||
#[msg("The hash is invalid")]
|
||||
HashInvalid,
|
||||
#[msg("Mining has not started yet")]
|
||||
NotStarted,
|
||||
#[msg("The epoch has ended and needs to be reset")]
|
||||
EpochNeedsReset,
|
||||
#[msg("An invalid bus account was provided")]
|
||||
BusInvalid,
|
||||
#[msg("This bus does not have enough tokens to pay the reward")]
|
||||
BusInsufficientFunds,
|
||||
#[msg("The signer is not authorized to perform this action")]
|
||||
NotAuthorized,
|
||||
#[msg("You cannot claim more tokens than are available")]
|
||||
ClaimTooLarge,
|
||||
#[msg("The treasury does not have enough tokens to honor the claim")]
|
||||
TreasuryInsufficientFunds,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anchor_lang::{
|
||||
prelude::Pubkey,
|
||||
solana_program::keccak::{hashv, Hash},
|
||||
};
|
||||
|
||||
use crate::{calculate_new_reward_rate, validate_hash, SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS};
|
||||
|
||||
#[test]
|
||||
fn test_validate_hash_pass() {
|
||||
let h1 = Hash::new_from_array([1; 32]);
|
||||
let signer = Pubkey::new_unique();
|
||||
let nonce = 10u64;
|
||||
let difficulty = Hash::new_from_array([255; 32]);
|
||||
let h2 = hashv(&[
|
||||
h1.to_bytes().as_slice(),
|
||||
signer.to_bytes().as_slice(),
|
||||
nonce.to_be_bytes().as_slice(),
|
||||
]);
|
||||
let res = validate_hash(h1, h2, signer, nonce, difficulty);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_hash_fail() {
|
||||
let h1 = Hash::new_from_array([1; 32]);
|
||||
let signer = Pubkey::new_unique();
|
||||
let nonce = 10u64;
|
||||
let difficulty = Hash::new_from_array([255; 32]);
|
||||
let h2 = Hash::new_from_array([2; 32]);
|
||||
let res = validate_hash(h1, h2, signer, nonce, difficulty);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_hash_fail_difficulty() {
|
||||
let h1 = Hash::new_from_array([1; 32]);
|
||||
let signer = Pubkey::new_unique();
|
||||
let nonce = 10u64;
|
||||
let difficulty = Hash::new_from_array([0; 32]);
|
||||
let h2 = hashv(&[
|
||||
h1.to_bytes().as_slice(),
|
||||
signer.to_bytes().as_slice(),
|
||||
nonce.to_be_bytes().as_slice(),
|
||||
]);
|
||||
let res = validate_hash(h1, h2, signer, nonce, difficulty);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_new_reward_rate_stable() {
|
||||
let current_rate = 1000;
|
||||
let new_rate = calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS);
|
||||
assert!(new_rate.eq(¤t_rate));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_new_reward_rate_no_chage() {
|
||||
let current_rate = 1000;
|
||||
let new_rate = calculate_new_reward_rate(current_rate, 0);
|
||||
assert!(new_rate.eq(¤t_rate));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_new_reward_rate_lower() {
|
||||
let current_rate = 1000;
|
||||
let new_rate =
|
||||
calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS.saturating_add(1_000_000));
|
||||
assert!(new_rate.lt(¤t_rate));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_new_reward_rate_higher() {
|
||||
let current_rate = 1000;
|
||||
let new_rate =
|
||||
calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS.saturating_sub(1_000_000));
|
||||
assert!(new_rate.gt(¤t_rate));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_new_reward_rate_max_smooth() {
|
||||
let current_rate = 1000;
|
||||
let new_rate = calculate_new_reward_rate(current_rate, 1);
|
||||
assert!(new_rate.eq(¤t_rate.saturating_mul(SMOOTHING_FACTOR)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_new_reward_rate_min_smooth() {
|
||||
let current_rate = 1000;
|
||||
let new_rate = calculate_new_reward_rate(current_rate, u64::MAX);
|
||||
assert!(new_rate.eq(¤t_rate.saturating_div(SMOOTHING_FACTOR)));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
[toolchain]
|
||||
channel = "1.70.0"
|
||||
# channel = "nightly"
|
||||
components = [ "rustfmt", "rust-analyzer" ]
|
||||
profile = "minimal"
|
||||
targets = [ "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-apple-darwin"]
|
||||
profile = "minimal"
|
||||
|
||||
103
src/instruction.rs
Normal file
103
src/instruction.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use num_enum::TryFromPrimitive;
|
||||
use shank::ShankInstruction;
|
||||
|
||||
#[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 = "bus_0", desc = "Ore bus account 0", writable)]
|
||||
#[account(3, name = "bus_1", desc = "Ore bus account 1", writable)]
|
||||
#[account(4, name = "bus_2", desc = "Ore bus account 2", writable)]
|
||||
#[account(5, name = "bus_3", desc = "Ore bus account 3", writable)]
|
||||
#[account(6, name = "bus_4", desc = "Ore bus account 4", writable)]
|
||||
#[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")]
|
||||
Epoch = 0,
|
||||
|
||||
#[account(0, name = "ore_program", desc = "Ore program")]
|
||||
#[account(1, name = "signer", desc = "Signer", signer)]
|
||||
#[account(2, name = "proof", desc = "Ore miner proof account", writable)]
|
||||
#[account(3, name = "system_program", desc = "Solana system program")]
|
||||
Proof = 1,
|
||||
|
||||
#[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 miner proof account", writable)]
|
||||
#[account(4, name = "treasury", desc = "Ore treasury account")]
|
||||
#[account(5, name = "token_program", desc = "SPL token program")]
|
||||
#[account(6, name = "slot_hashes", desc = "Solana slot hashes sysvar")]
|
||||
Mine = 2,
|
||||
|
||||
#[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 = "mint", desc = "Ore token mint account")]
|
||||
#[account(4, name = "proof", desc = "Ore miner proof account", writable)]
|
||||
#[account(5, name = "treasury", desc = "Ore treasury account", writable)]
|
||||
#[account(6, name = "treasury_tokens", desc = "Ore treasury token account", writable)]
|
||||
#[account(7, name = "token_program", desc = "SPL token program")]
|
||||
Claim = 3,
|
||||
|
||||
#[account(0, name = "ore_program", desc = "Ore program")]
|
||||
#[account(1, name = "admin", desc = "Admin signer", signer)]
|
||||
#[account(2, name = "bus_0", desc = "Ore bus account 0", writable)]
|
||||
#[account(3, name = "bus_1", desc = "Ore bus account 1", writable)]
|
||||
#[account(4, name = "bus_2", desc = "Ore bus account 2", writable)]
|
||||
#[account(5, name = "bus_3", desc = "Ore bus account 3", writable)]
|
||||
#[account(6, name = "bus_4", desc = "Ore bus account 4", writable)]
|
||||
#[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")]
|
||||
#[account(11, name = "treasury", desc = "Ore treasury account")]
|
||||
#[account(12, name = "treasury_tokens", desc = "Ore treasury token account", writable)]
|
||||
#[account(13, name = "system_program", desc = "Solana system program")]
|
||||
#[account(14, name = "token_program", desc = "SPL token program")]
|
||||
#[account(15, name = "rent", desc = "Solana rent sysvar")]
|
||||
Initialize = 100,
|
||||
// #[account(15, name = "associated_token_program", desc = "SPL associated token program")]
|
||||
|
||||
// TODO
|
||||
// #[account(0, name = "ore_program", desc = "Ore program")]
|
||||
// UpdateAdmin = 102,
|
||||
|
||||
// TODO
|
||||
// #[account(0, name = "ore_program", desc = "Ore program")]
|
||||
// UpdateDifficulty = 103,
|
||||
}
|
||||
|
||||
impl OreInstruction {
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
vec![*self as u8]
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
|
||||
pub struct InitializeArgs {
|
||||
pub bus_0_bump: u8,
|
||||
pub bus_1_bump: u8,
|
||||
pub bus_2_bump: u8,
|
||||
pub bus_3_bump: u8,
|
||||
pub bus_4_bump: u8,
|
||||
pub bus_5_bump: u8,
|
||||
pub bus_6_bump: u8,
|
||||
pub bus_7_bump: u8,
|
||||
pub mint_bump: u8,
|
||||
pub treasury_bump: u8,
|
||||
}
|
||||
|
||||
impl InitializeArgs {
|
||||
pub fn to_bytes(&self) -> &[u8] {
|
||||
bytemuck::bytes_of(self)
|
||||
}
|
||||
}
|
||||
514
src/lib.rs
Normal file
514
src/lib.rs
Normal file
@@ -0,0 +1,514 @@
|
||||
pub mod instruction;
|
||||
mod loaders;
|
||||
|
||||
use std::mem::size_of;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use solana_program::program_pack::Pack;
|
||||
use solana_program::{self, sysvar};
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
declare_id,
|
||||
entrypoint::ProgramResult,
|
||||
keccak::{hashv, Hash},
|
||||
program_error::ProgramError,
|
||||
pubkey,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
system_program,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
use instruction::*;
|
||||
use loaders::*;
|
||||
use spl_token::state::Mint;
|
||||
|
||||
// TODO Test admin and difficulty adjustment functions
|
||||
// TODO Use more decimals?
|
||||
|
||||
declare_id!("CeJShZEAzBLwtcLQvbZc7UT38e4nUTn63Za5UFyYYDTS");
|
||||
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
solana_program::entrypoint!(process_instruction);
|
||||
|
||||
// TODO Set this before deployment
|
||||
/// The unix timestamp after which mining is allowed.
|
||||
pub const START_AT: i64 = 0;
|
||||
|
||||
/// The initial reward rate to payout in the first epoch.
|
||||
pub const INITIAL_REWARD_RATE: u64 = 10u64.pow(3u32);
|
||||
|
||||
/// The initial hashing difficulty. The admin authority can update this in the future, if needed.
|
||||
pub const INITIAL_DIFFICULTY: Hash = Hash::new_from_array([
|
||||
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, 255,
|
||||
]);
|
||||
|
||||
/// The mint address of the ORE token.
|
||||
pub const MINT_ADDRESS: Pubkey = pubkey!("37TDfMS8NHpyhyCXBrY9m7rRrtj1f7TrFzD1iXqmTeUX");
|
||||
|
||||
/// 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 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
|
||||
);
|
||||
|
||||
pub fn process_instruction(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
data: &[u8],
|
||||
) -> ProgramResult {
|
||||
let (tag, data) = data
|
||||
.split_first()
|
||||
.ok_or(ProgramError::InvalidInstructionData)?;
|
||||
|
||||
let ix = OreInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))?;
|
||||
|
||||
match ix {
|
||||
OreInstruction::Epoch => process_epoch(program_id, accounts, data)?,
|
||||
OreInstruction::Proof => process_proof(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)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_epoch<'a, 'info>(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &'a [AccountInfo<'info>],
|
||||
data: &[u8],
|
||||
) -> ProgramResult {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_proof<'a, 'info>(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &'a [AccountInfo<'info>],
|
||||
data: &[u8],
|
||||
) -> ProgramResult {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_mine<'a, 'info>(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &'a [AccountInfo<'info>],
|
||||
data: &[u8],
|
||||
) -> ProgramResult {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_claim<'a, 'info>(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &'a [AccountInfo<'info>],
|
||||
data: &[u8],
|
||||
) -> ProgramResult {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_initialize<'a, 'info>(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &'a [AccountInfo<'info>],
|
||||
data: &[u8],
|
||||
) -> ProgramResult {
|
||||
let accounts_iter = &mut accounts.iter();
|
||||
let args = bytemuck::try_from_bytes::<InitializeArgs>(data)
|
||||
.or(Err(ProgramError::InvalidInstructionData))?;
|
||||
|
||||
// Account 1: Signer
|
||||
let signer = load_signer(next_account_info(accounts_iter)?)?;
|
||||
|
||||
// Accounts 2-9: Busses
|
||||
#[rustfmt::skip]
|
||||
let busses = vec![
|
||||
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[0], &[args.bus_0_bump]])?,
|
||||
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[1], &[args.bus_1_bump]])?,
|
||||
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[2], &[args.bus_2_bump]])?,
|
||||
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[3], &[args.bus_3_bump]])?,
|
||||
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[4], &[args.bus_4_bump]])?,
|
||||
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[5], &[args.bus_5_bump]])?,
|
||||
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[6], &[args.bus_6_bump]])?,
|
||||
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[7], &[args.bus_7_bump]])?,
|
||||
];
|
||||
|
||||
// Account 10: Mint
|
||||
#[rustfmt::skip]
|
||||
let mint = load_uninitialized_pda(next_account_info(accounts_iter)?, &[MINT, &[args.mint_bump]])?;
|
||||
|
||||
// Account 11: Treasury
|
||||
#[rustfmt::skip]
|
||||
let treasury_account_info = load_uninitialized_pda(next_account_info(accounts_iter)?, &[TREASURY, &[args.treasury_bump]])?;
|
||||
|
||||
// Account 12: Treasury tokens
|
||||
let treasury_tokens = load_uninitialized_account(next_account_info(accounts_iter)?)?;
|
||||
|
||||
// Account 13: System program
|
||||
let system_program = load_account(next_account_info(accounts_iter)?, system_program::id())?;
|
||||
|
||||
// Account 14: Token program
|
||||
let token_program = load_account(next_account_info(accounts_iter)?, spl_token::id())?;
|
||||
|
||||
// Account 15: Rent sysvar
|
||||
let rent_sysvar = load_account(next_account_info(accounts_iter)?, sysvar::rent::id())?;
|
||||
|
||||
// Initialize bus accounts
|
||||
let bus_bumps = vec![
|
||||
args.bus_0_bump,
|
||||
args.bus_1_bump,
|
||||
args.bus_2_bump,
|
||||
args.bus_3_bump,
|
||||
args.bus_4_bump,
|
||||
args.bus_5_bump,
|
||||
args.bus_6_bump,
|
||||
args.bus_7_bump,
|
||||
];
|
||||
for i in 0..BUS_COUNT {
|
||||
create_pda(
|
||||
busses[i],
|
||||
&crate::id(),
|
||||
size_of::<Bus>(),
|
||||
&[BUS, &[i as u8], &[bus_bumps[i]]],
|
||||
system_program,
|
||||
signer,
|
||||
)?;
|
||||
busses[i].try_borrow_mut_data()?.copy_from_slice(
|
||||
Bus {
|
||||
bump: bus_bumps[i] as u32,
|
||||
id: i as u32,
|
||||
available_rewards: 0,
|
||||
}
|
||||
.to_bytes(),
|
||||
);
|
||||
}
|
||||
|
||||
// Initialize treasury
|
||||
create_pda(
|
||||
treasury_account_info,
|
||||
&crate::id(),
|
||||
size_of::<Treasury>(),
|
||||
&[TREASURY, &[args.treasury_bump]],
|
||||
system_program,
|
||||
signer,
|
||||
)?;
|
||||
let mut treasury_data = treasury_account_info.data.borrow_mut();
|
||||
let mut treasury = bytemuck::try_from_bytes_mut::<Treasury>(&mut treasury_data).unwrap();
|
||||
treasury.bump = args.treasury_bump as u64;
|
||||
treasury.admin = *signer.key;
|
||||
treasury.epoch_start_at = 0;
|
||||
treasury.reward_rate = INITIAL_REWARD_RATE;
|
||||
treasury.total_claimed_rewards = 0;
|
||||
|
||||
// Initialize mint
|
||||
create_pda(
|
||||
mint,
|
||||
&spl_token::id(),
|
||||
Mint::LEN,
|
||||
&[MINT, &[args.mint_bump]],
|
||||
system_program,
|
||||
signer,
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&spl_token::instruction::initialize_mint(
|
||||
&spl_token::id(),
|
||||
mint.key,
|
||||
treasury_account_info.key,
|
||||
None,
|
||||
TOKEN_DECIMALS,
|
||||
)?,
|
||||
&[
|
||||
token_program.clone(),
|
||||
mint.clone(),
|
||||
treasury_account_info.clone(),
|
||||
rent_sysvar.clone(),
|
||||
],
|
||||
&[&[MINT, &[args.mint_bump]]],
|
||||
)?;
|
||||
|
||||
// TODO Initialize treasury token account
|
||||
create_pda(
|
||||
mint,
|
||||
&spl_token::id(),
|
||||
spl_token::state::Account::LEN,
|
||||
&[MINT, &[args.mint_bump]],
|
||||
system_program,
|
||||
signer,
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&spl_token::instruction::initialize_mint(
|
||||
&spl_token::id(),
|
||||
mint.key,
|
||||
treasury_account_info.key,
|
||||
None,
|
||||
TOKEN_DECIMALS,
|
||||
)?,
|
||||
&[
|
||||
token_program.clone(),
|
||||
mint.clone(),
|
||||
treasury_account_info.clone(),
|
||||
rent_sysvar.clone(),
|
||||
],
|
||||
&[&[MINT, &[args.mint_bump]]],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_hash(
|
||||
current_hash: Hash,
|
||||
hash: Hash,
|
||||
signer: Pubkey,
|
||||
nonce: u64,
|
||||
difficulty: Hash,
|
||||
) -> Result<(), ProgramError> {
|
||||
// Validate hash correctness.
|
||||
let hash_ = hashv(&[
|
||||
current_hash.as_ref(),
|
||||
signer.as_ref(),
|
||||
nonce.to_be_bytes().as_slice(),
|
||||
]);
|
||||
if !hash.eq(&hash_) {
|
||||
return Err(ProgramError::Custom(1));
|
||||
}
|
||||
|
||||
// Validate hash difficulty.
|
||||
if !hash.le(&difficulty) {
|
||||
return Err(ProgramError::Custom(1));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) -> u64 {
|
||||
// Avoid division by zero. Leave the reward rate unchanged.
|
||||
if epoch_rewards.eq(&0) {
|
||||
return current_rate;
|
||||
}
|
||||
|
||||
// Calculate new reward rate.
|
||||
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 to not change by more than a constant factor from one epoch to the next.
|
||||
let new_rate_min = current_rate.saturating_div(SMOOTHING_FACTOR);
|
||||
let new_rate_max = current_rate.saturating_mul(SMOOTHING_FACTOR);
|
||||
let new_rate_smoothed = new_rate_min.max(new_rate_max.min(new_rate));
|
||||
|
||||
// Prevent reward rate from dropping below 1 or exceeding BUS_EPOCH_REWARDS and return.
|
||||
new_rate_smoothed.max(1).min(BUS_EPOCH_REWARDS)
|
||||
}
|
||||
|
||||
/// Creates a new pda
|
||||
#[inline(always)]
|
||||
pub fn create_pda<'a, 'info>(
|
||||
target_account: &'a AccountInfo<'info>,
|
||||
owner: &Pubkey,
|
||||
space: usize,
|
||||
pda_seeds: &[&[u8]],
|
||||
system_program: &'a AccountInfo<'info>,
|
||||
payer: &'a AccountInfo<'info>,
|
||||
) -> ProgramResult {
|
||||
let rent = Rent::get()?;
|
||||
solana_program::program::invoke_signed(
|
||||
&solana_program::system_instruction::create_account(
|
||||
payer.key,
|
||||
target_account.key,
|
||||
rent.minimum_balance(space as usize),
|
||||
space as u64,
|
||||
owner,
|
||||
),
|
||||
&[
|
||||
payer.clone(),
|
||||
target_account.clone(),
|
||||
system_program.clone(),
|
||||
],
|
||||
&[pda_seeds],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The seed of the bus account PDA.
|
||||
pub const BUS: &[u8] = b"bus";
|
||||
|
||||
/// The seed of the mint account PDA.
|
||||
pub const MINT: &[u8] = b"mint";
|
||||
|
||||
/// The seed of the proof account PDA.
|
||||
pub const PROOF: &[u8] = b"proof";
|
||||
|
||||
/// The seed of the treasury account PDA.
|
||||
pub const TREASURY: &[u8] = b"treasury";
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
|
||||
pub struct Bus {
|
||||
/// The bump of the bus account PDA.
|
||||
pub bump: u32,
|
||||
|
||||
/// The ID of the bus account.
|
||||
pub id: u32,
|
||||
|
||||
/// The quantity of rewards this bus can issue in the current epoch epoch.
|
||||
pub available_rewards: u64,
|
||||
}
|
||||
|
||||
impl Bus {
|
||||
pub fn to_bytes(&self) -> &[u8] {
|
||||
bytemuck::bytes_of(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
|
||||
pub struct Treasury {
|
||||
/// The bump of the treasury account PDA.
|
||||
pub bump: u64,
|
||||
|
||||
/// The admin authority with permission to update the difficulty.
|
||||
pub admin: Pubkey,
|
||||
|
||||
/// The hash difficulty.
|
||||
// pub difficulty: Hash,
|
||||
|
||||
/// The timestamp of the start of the current epoch.
|
||||
pub epoch_start_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 Treasury {
|
||||
pub fn to_bytes(&self) -> &[u8] {
|
||||
bytemuck::bytes_of(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use solana_program::{
|
||||
keccak::{hashv, Hash},
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
use crate::{calculate_new_reward_rate, validate_hash, SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS};
|
||||
|
||||
#[test]
|
||||
fn test_validate_hash_pass() {
|
||||
let h1 = Hash::new_from_array([1; 32]);
|
||||
let signer = Pubkey::new_unique();
|
||||
let nonce = 10u64;
|
||||
let difficulty = Hash::new_from_array([255; 32]);
|
||||
let h2 = hashv(&[
|
||||
h1.to_bytes().as_slice(),
|
||||
signer.to_bytes().as_slice(),
|
||||
nonce.to_be_bytes().as_slice(),
|
||||
]);
|
||||
let res = validate_hash(h1, h2, signer, nonce, difficulty);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_hash_fail() {
|
||||
let h1 = Hash::new_from_array([1; 32]);
|
||||
let signer = Pubkey::new_unique();
|
||||
let nonce = 10u64;
|
||||
let difficulty = Hash::new_from_array([255; 32]);
|
||||
let h2 = Hash::new_from_array([2; 32]);
|
||||
let res = validate_hash(h1, h2, signer, nonce, difficulty);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_hash_fail_difficulty() {
|
||||
let h1 = Hash::new_from_array([1; 32]);
|
||||
let signer = Pubkey::new_unique();
|
||||
let nonce = 10u64;
|
||||
let difficulty = Hash::new_from_array([0; 32]);
|
||||
let h2 = hashv(&[
|
||||
h1.to_bytes().as_slice(),
|
||||
signer.to_bytes().as_slice(),
|
||||
nonce.to_be_bytes().as_slice(),
|
||||
]);
|
||||
let res = validate_hash(h1, h2, signer, nonce, difficulty);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_new_reward_rate_stable() {
|
||||
let current_rate = 1000;
|
||||
let new_rate = calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS);
|
||||
assert!(new_rate.eq(¤t_rate));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_new_reward_rate_no_chage() {
|
||||
let current_rate = 1000;
|
||||
let new_rate = calculate_new_reward_rate(current_rate, 0);
|
||||
assert!(new_rate.eq(¤t_rate));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_new_reward_rate_lower() {
|
||||
let current_rate = 1000;
|
||||
let new_rate =
|
||||
calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS.saturating_add(1_000_000));
|
||||
assert!(new_rate.lt(¤t_rate));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_new_reward_rate_higher() {
|
||||
let current_rate = 1000;
|
||||
let new_rate =
|
||||
calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS.saturating_sub(1_000_000));
|
||||
assert!(new_rate.gt(¤t_rate));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_new_reward_rate_max_smooth() {
|
||||
let current_rate = 1000;
|
||||
let new_rate = calculate_new_reward_rate(current_rate, 1);
|
||||
assert!(new_rate.eq(¤t_rate.saturating_mul(SMOOTHING_FACTOR)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_new_reward_rate_min_smooth() {
|
||||
let current_rate = 1000;
|
||||
let new_rate = calculate_new_reward_rate(current_rate, u64::MAX);
|
||||
assert!(new_rate.eq(¤t_rate.saturating_div(SMOOTHING_FACTOR)));
|
||||
}
|
||||
}
|
||||
65
src/loaders.rs
Normal file
65
src/loaders.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use solana_program::{
|
||||
account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, system_program,
|
||||
};
|
||||
|
||||
pub fn load_signer<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
) -> Result<&'a AccountInfo<'info>, ProgramError> {
|
||||
if !info.is_signer {
|
||||
return Err(ProgramError::MissingRequiredSignature);
|
||||
}
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
pub fn load_pda<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
seeds: &[&[u8]],
|
||||
writable: bool,
|
||||
) -> Result<&'a AccountInfo<'info>, ProgramError> {
|
||||
let key = Pubkey::create_program_address(seeds, &crate::id())?;
|
||||
if !info.key.eq(&key) {
|
||||
return Err(ProgramError::InvalidSeeds);
|
||||
}
|
||||
if !info.owner.eq(&crate::id()) {
|
||||
return Err(ProgramError::InvalidAccountOwner);
|
||||
}
|
||||
if writable {
|
||||
if !info.is_writable {
|
||||
return Err(ProgramError::InvalidAccountData);
|
||||
}
|
||||
}
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
pub fn load_uninitialized_pda<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
seeds: &[&[u8]],
|
||||
) -> Result<&'a AccountInfo<'info>, ProgramError> {
|
||||
let key = Pubkey::create_program_address(seeds, &crate::id())?;
|
||||
if !info.key.eq(&key) {
|
||||
return Err(ProgramError::InvalidSeeds);
|
||||
}
|
||||
load_uninitialized_account(info)
|
||||
}
|
||||
|
||||
pub fn load_uninitialized_account<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
) -> Result<&'a AccountInfo<'info>, ProgramError> {
|
||||
if !info.is_writable {
|
||||
return Err(ProgramError::InvalidAccountData);
|
||||
}
|
||||
if !info.data_is_empty() || !info.owner.eq(&system_program::id()) {
|
||||
return Err(ProgramError::AccountAlreadyInitialized);
|
||||
}
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
pub fn load_account<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
key: Pubkey,
|
||||
) -> Result<&'a AccountInfo<'info>, ProgramError> {
|
||||
if !info.key.eq(&key) {
|
||||
return Err(ProgramError::InvalidAccountData);
|
||||
}
|
||||
Ok(info)
|
||||
}
|
||||
121
tests/test_initialize.rs
Normal file
121
tests/test_initialize.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use ore::{
|
||||
instruction::{InitializeArgs, OreInstruction},
|
||||
Bus, Treasury, BUS, BUS_COUNT, INITIAL_REWARD_RATE, MINT, TREASURY,
|
||||
};
|
||||
use solana_program::{
|
||||
hash::Hash,
|
||||
instruction::{AccountMeta, Instruction},
|
||||
program_option::COption,
|
||||
program_pack::Pack,
|
||||
pubkey::Pubkey,
|
||||
system_program, sysvar,
|
||||
};
|
||||
use solana_program_test::{processor, BanksClient, ProgramTest};
|
||||
use solana_sdk::{
|
||||
signature::{Keypair, Signer},
|
||||
transaction::Transaction,
|
||||
};
|
||||
use spl_token::state::Mint;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_initialize() {
|
||||
// Setup
|
||||
let (mut banks, payer, hash) = setup_program_test_env().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_pda = Pubkey::find_program_address(&[TREASURY], &ore::id());
|
||||
let treasury_tokens_address =
|
||||
spl_associated_token_account::get_associated_token_address(&treasury_pda.0, &mint_pda.0);
|
||||
|
||||
// Build ix
|
||||
let ix = Instruction {
|
||||
program_id: ore::ID,
|
||||
accounts: vec![
|
||||
AccountMeta::new(payer.pubkey(), 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_pda.0, false),
|
||||
AccountMeta::new(treasury_pda.0, false),
|
||||
AccountMeta::new(treasury_tokens_address, false),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
AccountMeta::new_readonly(spl_token::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||
],
|
||||
data: [
|
||||
OreInstruction::Initialize.to_vec(),
|
||||
InitializeArgs {
|
||||
bus_0_bump: bus_pdas[0].1,
|
||||
bus_1_bump: bus_pdas[1].1,
|
||||
bus_2_bump: bus_pdas[2].1,
|
||||
bus_3_bump: bus_pdas[3].1,
|
||||
bus_4_bump: bus_pdas[4].1,
|
||||
bus_5_bump: bus_pdas[5].1,
|
||||
bus_6_bump: bus_pdas[6].1,
|
||||
bus_7_bump: bus_pdas[7].1,
|
||||
mint_bump: mint_pda.1,
|
||||
treasury_bump: treasury_pda.1,
|
||||
}
|
||||
.to_bytes()
|
||||
.to_vec(),
|
||||
]
|
||||
.concat(),
|
||||
};
|
||||
|
||||
// Submit tx
|
||||
let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], hash);
|
||||
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 = bytemuck::try_from_bytes::<Bus>(&bus_account.data).unwrap();
|
||||
assert_eq!(bus.bump as u8, bus_pdas[i].1);
|
||||
assert_eq!(bus.id as u8, i as u8);
|
||||
assert_eq!(bus.available_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 = bytemuck::try_from_bytes::<Treasury>(&treasury_account.data).unwrap();
|
||||
assert_eq!(treasury.bump as u8, treasury_pda.1);
|
||||
assert_eq!(treasury.admin, payer.pubkey());
|
||||
assert_eq!(treasury.epoch_start_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_pda.0).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);
|
||||
}
|
||||
|
||||
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);
|
||||
program_test.start().await
|
||||
}
|
||||
Reference in New Issue
Block a user