├── .github └── workflows │ └── build-test-lint.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── release.toml ├── rustfmt.toml ├── src ├── api.rs ├── de.rs ├── deserializer │ ├── borsh.rs │ ├── floats.rs │ ├── mod.rs │ └── spl.rs ├── discriminator │ ├── match_discriminator.rs │ └── mod.rs ├── errors.rs ├── idl │ ├── encoder │ │ ├── idl_decoder.rs │ │ ├── idl_encoder.rs │ │ └── mod.rs │ ├── idl_address.rs │ ├── idl_provider.rs │ ├── idl_retriever.rs │ └── mod.rs ├── ixs │ ├── discriminator.rs │ ├── instruction_mapper.rs │ └── mod.rs ├── json │ ├── discriminator.rs │ ├── json_accounts_deserializer.rs │ ├── json_common.rs │ ├── json_idl_enum_variant_de.rs │ ├── json_idl_field_de.rs │ ├── json_idl_type_de.rs │ ├── json_idl_type_def_de.rs │ ├── json_serialization_opts.rs │ └── mod.rs ├── lib.rs └── traits.rs └── tests ├── deserialize_account.rs └── utils ├── deserialization.rs └── mod.rs /.github/workflows/build-test-lint.yml: -------------------------------------------------------------------------------- 1 | name: chainparser Build+Test 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | chainparser_build_and_test: 14 | defaults: 15 | run: 16 | shell: bash 17 | 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 90 20 | strategy: 21 | fail-fast: true 22 | 23 | steps: 24 | - uses: actions/checkout@master 25 | 26 | - uses: Swatinem/rust-cache@v2 27 | with: 28 | shared-key: 'chainparser-build' 29 | 30 | - name: Build and Run Tests 31 | run: | 32 | cargo build 33 | make test 34 | 35 | - name: cargo fmt 36 | run: | 37 | cargo fmt -- --check 38 | 39 | - name: cargo clippy 40 | run: | 41 | cargo clippy -- --deny=warnings 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "ahash" 13 | version = "0.7.8" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" 16 | dependencies = [ 17 | "getrandom 0.2.12", 18 | "once_cell", 19 | "version_check", 20 | ] 21 | 22 | [[package]] 23 | name = "ahash" 24 | version = "0.8.11" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 27 | dependencies = [ 28 | "cfg-if", 29 | "once_cell", 30 | "version_check", 31 | "zerocopy", 32 | ] 33 | 34 | [[package]] 35 | name = "aho-corasick" 36 | version = "1.1.2" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 39 | dependencies = [ 40 | "memchr", 41 | ] 42 | 43 | [[package]] 44 | name = "anchor-lang-idl" 45 | version = "0.1.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "b29da81eae478b1bb846749b06b8a2cb9c6f9ed26ca793b0c916793fdf36adab" 48 | dependencies = [ 49 | "anyhow", 50 | "serde", 51 | "serde_json", 52 | ] 53 | 54 | [[package]] 55 | name = "anyhow" 56 | version = "1.0.80" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" 59 | 60 | [[package]] 61 | name = "ark-bn254" 62 | version = "0.4.0" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" 65 | dependencies = [ 66 | "ark-ec", 67 | "ark-ff", 68 | "ark-std", 69 | ] 70 | 71 | [[package]] 72 | name = "ark-ec" 73 | version = "0.4.2" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" 76 | dependencies = [ 77 | "ark-ff", 78 | "ark-poly", 79 | "ark-serialize", 80 | "ark-std", 81 | "derivative", 82 | "hashbrown 0.13.2", 83 | "itertools", 84 | "num-traits", 85 | "zeroize", 86 | ] 87 | 88 | [[package]] 89 | name = "ark-ff" 90 | version = "0.4.2" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" 93 | dependencies = [ 94 | "ark-ff-asm", 95 | "ark-ff-macros", 96 | "ark-serialize", 97 | "ark-std", 98 | "derivative", 99 | "digest 0.10.7", 100 | "itertools", 101 | "num-bigint", 102 | "num-traits", 103 | "paste", 104 | "rustc_version", 105 | "zeroize", 106 | ] 107 | 108 | [[package]] 109 | name = "ark-ff-asm" 110 | version = "0.4.2" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" 113 | dependencies = [ 114 | "quote", 115 | "syn 1.0.109", 116 | ] 117 | 118 | [[package]] 119 | name = "ark-ff-macros" 120 | version = "0.4.2" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" 123 | dependencies = [ 124 | "num-bigint", 125 | "num-traits", 126 | "proc-macro2", 127 | "quote", 128 | "syn 1.0.109", 129 | ] 130 | 131 | [[package]] 132 | name = "ark-poly" 133 | version = "0.4.2" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" 136 | dependencies = [ 137 | "ark-ff", 138 | "ark-serialize", 139 | "ark-std", 140 | "derivative", 141 | "hashbrown 0.13.2", 142 | ] 143 | 144 | [[package]] 145 | name = "ark-serialize" 146 | version = "0.4.2" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" 149 | dependencies = [ 150 | "ark-serialize-derive", 151 | "ark-std", 152 | "digest 0.10.7", 153 | "num-bigint", 154 | ] 155 | 156 | [[package]] 157 | name = "ark-serialize-derive" 158 | version = "0.4.2" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" 161 | dependencies = [ 162 | "proc-macro2", 163 | "quote", 164 | "syn 1.0.109", 165 | ] 166 | 167 | [[package]] 168 | name = "ark-std" 169 | version = "0.4.0" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" 172 | dependencies = [ 173 | "num-traits", 174 | "rand 0.8.5", 175 | ] 176 | 177 | [[package]] 178 | name = "arrayref" 179 | version = "0.3.7" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" 182 | 183 | [[package]] 184 | name = "arrayvec" 185 | version = "0.7.4" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" 188 | 189 | [[package]] 190 | name = "assert_matches" 191 | version = "1.5.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" 194 | 195 | [[package]] 196 | name = "atty" 197 | version = "0.2.14" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 200 | dependencies = [ 201 | "hermit-abi", 202 | "libc", 203 | "winapi", 204 | ] 205 | 206 | [[package]] 207 | name = "autocfg" 208 | version = "1.1.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 211 | 212 | [[package]] 213 | name = "base64" 214 | version = "0.12.3" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 217 | 218 | [[package]] 219 | name = "base64" 220 | version = "0.21.7" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 223 | 224 | [[package]] 225 | name = "base64" 226 | version = "0.22.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" 229 | 230 | [[package]] 231 | name = "bincode" 232 | version = "1.3.3" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 235 | dependencies = [ 236 | "serde", 237 | ] 238 | 239 | [[package]] 240 | name = "bitflags" 241 | version = "1.3.2" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 244 | 245 | [[package]] 246 | name = "bitflags" 247 | version = "2.4.2" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" 250 | dependencies = [ 251 | "serde", 252 | ] 253 | 254 | [[package]] 255 | name = "bitmaps" 256 | version = "2.1.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" 259 | dependencies = [ 260 | "typenum", 261 | ] 262 | 263 | [[package]] 264 | name = "blake3" 265 | version = "1.5.0" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" 268 | dependencies = [ 269 | "arrayref", 270 | "arrayvec", 271 | "cc", 272 | "cfg-if", 273 | "constant_time_eq", 274 | "digest 0.10.7", 275 | ] 276 | 277 | [[package]] 278 | name = "block-buffer" 279 | version = "0.9.0" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 282 | dependencies = [ 283 | "generic-array", 284 | ] 285 | 286 | [[package]] 287 | name = "block-buffer" 288 | version = "0.10.4" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 291 | dependencies = [ 292 | "generic-array", 293 | ] 294 | 295 | [[package]] 296 | name = "borsh" 297 | version = "0.9.3" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" 300 | dependencies = [ 301 | "borsh-derive 0.9.3", 302 | "hashbrown 0.11.2", 303 | ] 304 | 305 | [[package]] 306 | name = "borsh" 307 | version = "0.10.3" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" 310 | dependencies = [ 311 | "borsh-derive 0.10.3", 312 | "hashbrown 0.13.2", 313 | ] 314 | 315 | [[package]] 316 | name = "borsh" 317 | version = "1.3.1" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" 320 | dependencies = [ 321 | "borsh-derive 1.3.1", 322 | "cfg_aliases", 323 | ] 324 | 325 | [[package]] 326 | name = "borsh-derive" 327 | version = "0.9.3" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" 330 | dependencies = [ 331 | "borsh-derive-internal 0.9.3", 332 | "borsh-schema-derive-internal 0.9.3", 333 | "proc-macro-crate 0.1.5", 334 | "proc-macro2", 335 | "syn 1.0.109", 336 | ] 337 | 338 | [[package]] 339 | name = "borsh-derive" 340 | version = "0.10.3" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" 343 | dependencies = [ 344 | "borsh-derive-internal 0.10.3", 345 | "borsh-schema-derive-internal 0.10.3", 346 | "proc-macro-crate 0.1.5", 347 | "proc-macro2", 348 | "syn 1.0.109", 349 | ] 350 | 351 | [[package]] 352 | name = "borsh-derive" 353 | version = "1.3.1" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" 356 | dependencies = [ 357 | "once_cell", 358 | "proc-macro-crate 3.1.0", 359 | "proc-macro2", 360 | "quote", 361 | "syn 2.0.52", 362 | "syn_derive", 363 | ] 364 | 365 | [[package]] 366 | name = "borsh-derive-internal" 367 | version = "0.9.3" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" 370 | dependencies = [ 371 | "proc-macro2", 372 | "quote", 373 | "syn 1.0.109", 374 | ] 375 | 376 | [[package]] 377 | name = "borsh-derive-internal" 378 | version = "0.10.3" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" 381 | dependencies = [ 382 | "proc-macro2", 383 | "quote", 384 | "syn 1.0.109", 385 | ] 386 | 387 | [[package]] 388 | name = "borsh-schema-derive-internal" 389 | version = "0.9.3" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" 392 | dependencies = [ 393 | "proc-macro2", 394 | "quote", 395 | "syn 1.0.109", 396 | ] 397 | 398 | [[package]] 399 | name = "borsh-schema-derive-internal" 400 | version = "0.10.3" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" 403 | dependencies = [ 404 | "proc-macro2", 405 | "quote", 406 | "syn 1.0.109", 407 | ] 408 | 409 | [[package]] 410 | name = "bs58" 411 | version = "0.4.0" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" 414 | 415 | [[package]] 416 | name = "bumpalo" 417 | version = "3.15.3" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" 420 | 421 | [[package]] 422 | name = "bv" 423 | version = "0.11.1" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" 426 | dependencies = [ 427 | "feature-probe", 428 | "serde", 429 | ] 430 | 431 | [[package]] 432 | name = "bytemuck" 433 | version = "1.14.3" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" 436 | dependencies = [ 437 | "bytemuck_derive", 438 | ] 439 | 440 | [[package]] 441 | name = "bytemuck_derive" 442 | version = "1.5.0" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" 445 | dependencies = [ 446 | "proc-macro2", 447 | "quote", 448 | "syn 2.0.52", 449 | ] 450 | 451 | [[package]] 452 | name = "byteorder" 453 | version = "1.5.0" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 456 | 457 | [[package]] 458 | name = "cc" 459 | version = "1.0.89" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723" 462 | dependencies = [ 463 | "jobserver", 464 | "libc", 465 | ] 466 | 467 | [[package]] 468 | name = "cfg-if" 469 | version = "1.0.0" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 472 | 473 | [[package]] 474 | name = "cfg_aliases" 475 | version = "0.1.1" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" 478 | 479 | [[package]] 480 | name = "chainparser" 481 | version = "0.3.0" 482 | dependencies = [ 483 | "arrayref", 484 | "base64 0.22.0", 485 | "borsh 0.9.3", 486 | "flate2", 487 | "heck", 488 | "lazy_static", 489 | "log", 490 | "serde", 491 | "serde_json", 492 | "solana-sdk", 493 | "solana_idl", 494 | "thiserror", 495 | ] 496 | 497 | [[package]] 498 | name = "chrono" 499 | version = "0.4.34" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" 502 | dependencies = [ 503 | "num-traits", 504 | ] 505 | 506 | [[package]] 507 | name = "console_error_panic_hook" 508 | version = "0.1.7" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" 511 | dependencies = [ 512 | "cfg-if", 513 | "wasm-bindgen", 514 | ] 515 | 516 | [[package]] 517 | name = "console_log" 518 | version = "0.2.2" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f" 521 | dependencies = [ 522 | "log", 523 | "web-sys", 524 | ] 525 | 526 | [[package]] 527 | name = "constant_time_eq" 528 | version = "0.3.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" 531 | 532 | [[package]] 533 | name = "cpufeatures" 534 | version = "0.2.12" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 537 | dependencies = [ 538 | "libc", 539 | ] 540 | 541 | [[package]] 542 | name = "crc32fast" 543 | version = "1.4.0" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" 546 | dependencies = [ 547 | "cfg-if", 548 | ] 549 | 550 | [[package]] 551 | name = "crossbeam-deque" 552 | version = "0.8.5" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 555 | dependencies = [ 556 | "crossbeam-epoch", 557 | "crossbeam-utils", 558 | ] 559 | 560 | [[package]] 561 | name = "crossbeam-epoch" 562 | version = "0.9.18" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 565 | dependencies = [ 566 | "crossbeam-utils", 567 | ] 568 | 569 | [[package]] 570 | name = "crossbeam-utils" 571 | version = "0.8.19" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" 574 | 575 | [[package]] 576 | name = "crunchy" 577 | version = "0.2.2" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 580 | 581 | [[package]] 582 | name = "crypto-common" 583 | version = "0.1.6" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 586 | dependencies = [ 587 | "generic-array", 588 | "typenum", 589 | ] 590 | 591 | [[package]] 592 | name = "crypto-mac" 593 | version = "0.8.0" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" 596 | dependencies = [ 597 | "generic-array", 598 | "subtle", 599 | ] 600 | 601 | [[package]] 602 | name = "curve25519-dalek" 603 | version = "3.2.1" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" 606 | dependencies = [ 607 | "byteorder", 608 | "digest 0.9.0", 609 | "rand_core 0.5.1", 610 | "serde", 611 | "subtle", 612 | "zeroize", 613 | ] 614 | 615 | [[package]] 616 | name = "darling" 617 | version = "0.20.8" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" 620 | dependencies = [ 621 | "darling_core", 622 | "darling_macro", 623 | ] 624 | 625 | [[package]] 626 | name = "darling_core" 627 | version = "0.20.8" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" 630 | dependencies = [ 631 | "fnv", 632 | "ident_case", 633 | "proc-macro2", 634 | "quote", 635 | "strsim", 636 | "syn 2.0.52", 637 | ] 638 | 639 | [[package]] 640 | name = "darling_macro" 641 | version = "0.20.8" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" 644 | dependencies = [ 645 | "darling_core", 646 | "quote", 647 | "syn 2.0.52", 648 | ] 649 | 650 | [[package]] 651 | name = "derivation-path" 652 | version = "0.2.0" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" 655 | 656 | [[package]] 657 | name = "derivative" 658 | version = "2.2.0" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" 661 | dependencies = [ 662 | "proc-macro2", 663 | "quote", 664 | "syn 1.0.109", 665 | ] 666 | 667 | [[package]] 668 | name = "digest" 669 | version = "0.9.0" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 672 | dependencies = [ 673 | "generic-array", 674 | ] 675 | 676 | [[package]] 677 | name = "digest" 678 | version = "0.10.7" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 681 | dependencies = [ 682 | "block-buffer 0.10.4", 683 | "crypto-common", 684 | "subtle", 685 | ] 686 | 687 | [[package]] 688 | name = "ed25519" 689 | version = "1.5.3" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" 692 | dependencies = [ 693 | "signature", 694 | ] 695 | 696 | [[package]] 697 | name = "ed25519-dalek" 698 | version = "1.0.1" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" 701 | dependencies = [ 702 | "curve25519-dalek", 703 | "ed25519", 704 | "rand 0.7.3", 705 | "serde", 706 | "sha2 0.9.9", 707 | "zeroize", 708 | ] 709 | 710 | [[package]] 711 | name = "ed25519-dalek-bip32" 712 | version = "0.2.0" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" 715 | dependencies = [ 716 | "derivation-path", 717 | "ed25519-dalek", 718 | "hmac 0.12.1", 719 | "sha2 0.10.8", 720 | ] 721 | 722 | [[package]] 723 | name = "either" 724 | version = "1.10.0" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" 727 | 728 | [[package]] 729 | name = "env_logger" 730 | version = "0.9.3" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" 733 | dependencies = [ 734 | "atty", 735 | "humantime", 736 | "log", 737 | "regex", 738 | "termcolor", 739 | ] 740 | 741 | [[package]] 742 | name = "equivalent" 743 | version = "1.0.1" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 746 | 747 | [[package]] 748 | name = "feature-probe" 749 | version = "0.1.1" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" 752 | 753 | [[package]] 754 | name = "flate2" 755 | version = "1.0.28" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" 758 | dependencies = [ 759 | "crc32fast", 760 | "miniz_oxide", 761 | ] 762 | 763 | [[package]] 764 | name = "fnv" 765 | version = "1.0.7" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 768 | 769 | [[package]] 770 | name = "generic-array" 771 | version = "0.14.7" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 774 | dependencies = [ 775 | "serde", 776 | "typenum", 777 | "version_check", 778 | ] 779 | 780 | [[package]] 781 | name = "getrandom" 782 | version = "0.1.16" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 785 | dependencies = [ 786 | "cfg-if", 787 | "js-sys", 788 | "libc", 789 | "wasi 0.9.0+wasi-snapshot-preview1", 790 | "wasm-bindgen", 791 | ] 792 | 793 | [[package]] 794 | name = "getrandom" 795 | version = "0.2.12" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" 798 | dependencies = [ 799 | "cfg-if", 800 | "js-sys", 801 | "libc", 802 | "wasi 0.11.0+wasi-snapshot-preview1", 803 | "wasm-bindgen", 804 | ] 805 | 806 | [[package]] 807 | name = "hashbrown" 808 | version = "0.11.2" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 811 | dependencies = [ 812 | "ahash 0.7.8", 813 | ] 814 | 815 | [[package]] 816 | name = "hashbrown" 817 | version = "0.13.2" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" 820 | dependencies = [ 821 | "ahash 0.8.11", 822 | ] 823 | 824 | [[package]] 825 | name = "hashbrown" 826 | version = "0.14.3" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 829 | 830 | [[package]] 831 | name = "heck" 832 | version = "0.5.0" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 835 | 836 | [[package]] 837 | name = "hermit-abi" 838 | version = "0.1.19" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 841 | dependencies = [ 842 | "libc", 843 | ] 844 | 845 | [[package]] 846 | name = "hmac" 847 | version = "0.8.1" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" 850 | dependencies = [ 851 | "crypto-mac", 852 | "digest 0.9.0", 853 | ] 854 | 855 | [[package]] 856 | name = "hmac" 857 | version = "0.12.1" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 860 | dependencies = [ 861 | "digest 0.10.7", 862 | ] 863 | 864 | [[package]] 865 | name = "hmac-drbg" 866 | version = "0.3.0" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" 869 | dependencies = [ 870 | "digest 0.9.0", 871 | "generic-array", 872 | "hmac 0.8.1", 873 | ] 874 | 875 | [[package]] 876 | name = "humantime" 877 | version = "2.1.0" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 880 | 881 | [[package]] 882 | name = "ident_case" 883 | version = "1.0.1" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 886 | 887 | [[package]] 888 | name = "im" 889 | version = "15.1.0" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" 892 | dependencies = [ 893 | "bitmaps", 894 | "rand_core 0.6.4", 895 | "rand_xoshiro", 896 | "rayon", 897 | "serde", 898 | "sized-chunks", 899 | "typenum", 900 | "version_check", 901 | ] 902 | 903 | [[package]] 904 | name = "indexmap" 905 | version = "2.2.5" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" 908 | dependencies = [ 909 | "equivalent", 910 | "hashbrown 0.14.3", 911 | ] 912 | 913 | [[package]] 914 | name = "itertools" 915 | version = "0.10.5" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 918 | dependencies = [ 919 | "either", 920 | ] 921 | 922 | [[package]] 923 | name = "itoa" 924 | version = "1.0.10" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 927 | 928 | [[package]] 929 | name = "jobserver" 930 | version = "0.1.28" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" 933 | dependencies = [ 934 | "libc", 935 | ] 936 | 937 | [[package]] 938 | name = "js-sys" 939 | version = "0.3.69" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 942 | dependencies = [ 943 | "wasm-bindgen", 944 | ] 945 | 946 | [[package]] 947 | name = "keccak" 948 | version = "0.1.5" 949 | source = "registry+https://github.com/rust-lang/crates.io-index" 950 | checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" 951 | dependencies = [ 952 | "cpufeatures", 953 | ] 954 | 955 | [[package]] 956 | name = "lazy_static" 957 | version = "1.5.0" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 960 | 961 | [[package]] 962 | name = "libc" 963 | version = "0.2.153" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 966 | 967 | [[package]] 968 | name = "libsecp256k1" 969 | version = "0.6.0" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" 972 | dependencies = [ 973 | "arrayref", 974 | "base64 0.12.3", 975 | "digest 0.9.0", 976 | "hmac-drbg", 977 | "libsecp256k1-core", 978 | "libsecp256k1-gen-ecmult", 979 | "libsecp256k1-gen-genmult", 980 | "rand 0.7.3", 981 | "serde", 982 | "sha2 0.9.9", 983 | "typenum", 984 | ] 985 | 986 | [[package]] 987 | name = "libsecp256k1-core" 988 | version = "0.2.2" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" 991 | dependencies = [ 992 | "crunchy", 993 | "digest 0.9.0", 994 | "subtle", 995 | ] 996 | 997 | [[package]] 998 | name = "libsecp256k1-gen-ecmult" 999 | version = "0.2.1" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" 1002 | dependencies = [ 1003 | "libsecp256k1-core", 1004 | ] 1005 | 1006 | [[package]] 1007 | name = "libsecp256k1-gen-genmult" 1008 | version = "0.2.1" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" 1011 | dependencies = [ 1012 | "libsecp256k1-core", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "light-poseidon" 1017 | version = "0.2.0" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" 1020 | dependencies = [ 1021 | "ark-bn254", 1022 | "ark-ff", 1023 | "num-bigint", 1024 | "thiserror", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "lock_api" 1029 | version = "0.4.11" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 1032 | dependencies = [ 1033 | "autocfg", 1034 | "scopeguard", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "log" 1039 | version = "0.4.21" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 1042 | 1043 | [[package]] 1044 | name = "memchr" 1045 | version = "2.7.1" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 1048 | 1049 | [[package]] 1050 | name = "memmap2" 1051 | version = "0.5.10" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" 1054 | dependencies = [ 1055 | "libc", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "memoffset" 1060 | version = "0.9.0" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" 1063 | dependencies = [ 1064 | "autocfg", 1065 | ] 1066 | 1067 | [[package]] 1068 | name = "miniz_oxide" 1069 | version = "0.7.2" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 1072 | dependencies = [ 1073 | "adler", 1074 | ] 1075 | 1076 | [[package]] 1077 | name = "num-bigint" 1078 | version = "0.4.4" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" 1081 | dependencies = [ 1082 | "autocfg", 1083 | "num-integer", 1084 | "num-traits", 1085 | ] 1086 | 1087 | [[package]] 1088 | name = "num-derive" 1089 | version = "0.4.2" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" 1092 | dependencies = [ 1093 | "proc-macro2", 1094 | "quote", 1095 | "syn 2.0.52", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "num-integer" 1100 | version = "0.1.46" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1103 | dependencies = [ 1104 | "num-traits", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "num-traits" 1109 | version = "0.2.18" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" 1112 | dependencies = [ 1113 | "autocfg", 1114 | ] 1115 | 1116 | [[package]] 1117 | name = "num_enum" 1118 | version = "0.7.2" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" 1121 | dependencies = [ 1122 | "num_enum_derive", 1123 | ] 1124 | 1125 | [[package]] 1126 | name = "num_enum_derive" 1127 | version = "0.7.2" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" 1130 | dependencies = [ 1131 | "proc-macro-crate 3.1.0", 1132 | "proc-macro2", 1133 | "quote", 1134 | "syn 2.0.52", 1135 | ] 1136 | 1137 | [[package]] 1138 | name = "once_cell" 1139 | version = "1.19.0" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 1142 | 1143 | [[package]] 1144 | name = "opaque-debug" 1145 | version = "0.3.1" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 1148 | 1149 | [[package]] 1150 | name = "parking_lot" 1151 | version = "0.12.1" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 1154 | dependencies = [ 1155 | "lock_api", 1156 | "parking_lot_core", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "parking_lot_core" 1161 | version = "0.9.9" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 1164 | dependencies = [ 1165 | "cfg-if", 1166 | "libc", 1167 | "redox_syscall", 1168 | "smallvec", 1169 | "windows-targets", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "paste" 1174 | version = "1.0.14" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" 1177 | 1178 | [[package]] 1179 | name = "pbkdf2" 1180 | version = "0.4.0" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" 1183 | dependencies = [ 1184 | "crypto-mac", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "pbkdf2" 1189 | version = "0.11.0" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" 1192 | dependencies = [ 1193 | "digest 0.10.7", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "percent-encoding" 1198 | version = "2.3.1" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1201 | 1202 | [[package]] 1203 | name = "ppv-lite86" 1204 | version = "0.2.17" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1207 | 1208 | [[package]] 1209 | name = "proc-macro-crate" 1210 | version = "0.1.5" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" 1213 | dependencies = [ 1214 | "toml", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "proc-macro-crate" 1219 | version = "3.1.0" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" 1222 | dependencies = [ 1223 | "toml_edit", 1224 | ] 1225 | 1226 | [[package]] 1227 | name = "proc-macro-error" 1228 | version = "1.0.4" 1229 | source = "registry+https://github.com/rust-lang/crates.io-index" 1230 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1231 | dependencies = [ 1232 | "proc-macro-error-attr", 1233 | "proc-macro2", 1234 | "quote", 1235 | "version_check", 1236 | ] 1237 | 1238 | [[package]] 1239 | name = "proc-macro-error-attr" 1240 | version = "1.0.4" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1243 | dependencies = [ 1244 | "proc-macro2", 1245 | "quote", 1246 | "version_check", 1247 | ] 1248 | 1249 | [[package]] 1250 | name = "proc-macro2" 1251 | version = "1.0.78" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 1254 | dependencies = [ 1255 | "unicode-ident", 1256 | ] 1257 | 1258 | [[package]] 1259 | name = "qstring" 1260 | version = "0.7.2" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" 1263 | dependencies = [ 1264 | "percent-encoding", 1265 | ] 1266 | 1267 | [[package]] 1268 | name = "qualifier_attr" 1269 | version = "0.2.2" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" 1272 | dependencies = [ 1273 | "proc-macro2", 1274 | "quote", 1275 | "syn 2.0.52", 1276 | ] 1277 | 1278 | [[package]] 1279 | name = "quote" 1280 | version = "1.0.35" 1281 | source = "registry+https://github.com/rust-lang/crates.io-index" 1282 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 1283 | dependencies = [ 1284 | "proc-macro2", 1285 | ] 1286 | 1287 | [[package]] 1288 | name = "rand" 1289 | version = "0.7.3" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 1292 | dependencies = [ 1293 | "getrandom 0.1.16", 1294 | "libc", 1295 | "rand_chacha 0.2.2", 1296 | "rand_core 0.5.1", 1297 | "rand_hc", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "rand" 1302 | version = "0.8.5" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1305 | dependencies = [ 1306 | "libc", 1307 | "rand_chacha 0.3.1", 1308 | "rand_core 0.6.4", 1309 | ] 1310 | 1311 | [[package]] 1312 | name = "rand_chacha" 1313 | version = "0.2.2" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 1316 | dependencies = [ 1317 | "ppv-lite86", 1318 | "rand_core 0.5.1", 1319 | ] 1320 | 1321 | [[package]] 1322 | name = "rand_chacha" 1323 | version = "0.3.1" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1326 | dependencies = [ 1327 | "ppv-lite86", 1328 | "rand_core 0.6.4", 1329 | ] 1330 | 1331 | [[package]] 1332 | name = "rand_core" 1333 | version = "0.5.1" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 1336 | dependencies = [ 1337 | "getrandom 0.1.16", 1338 | ] 1339 | 1340 | [[package]] 1341 | name = "rand_core" 1342 | version = "0.6.4" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1345 | dependencies = [ 1346 | "getrandom 0.2.12", 1347 | ] 1348 | 1349 | [[package]] 1350 | name = "rand_hc" 1351 | version = "0.2.0" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 1354 | dependencies = [ 1355 | "rand_core 0.5.1", 1356 | ] 1357 | 1358 | [[package]] 1359 | name = "rand_xoshiro" 1360 | version = "0.6.0" 1361 | source = "registry+https://github.com/rust-lang/crates.io-index" 1362 | checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" 1363 | dependencies = [ 1364 | "rand_core 0.6.4", 1365 | ] 1366 | 1367 | [[package]] 1368 | name = "rayon" 1369 | version = "1.9.0" 1370 | source = "registry+https://github.com/rust-lang/crates.io-index" 1371 | checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" 1372 | dependencies = [ 1373 | "either", 1374 | "rayon-core", 1375 | ] 1376 | 1377 | [[package]] 1378 | name = "rayon-core" 1379 | version = "1.12.1" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 1382 | dependencies = [ 1383 | "crossbeam-deque", 1384 | "crossbeam-utils", 1385 | ] 1386 | 1387 | [[package]] 1388 | name = "redox_syscall" 1389 | version = "0.4.1" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 1392 | dependencies = [ 1393 | "bitflags 1.3.2", 1394 | ] 1395 | 1396 | [[package]] 1397 | name = "regex" 1398 | version = "1.10.3" 1399 | source = "registry+https://github.com/rust-lang/crates.io-index" 1400 | checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" 1401 | dependencies = [ 1402 | "aho-corasick", 1403 | "memchr", 1404 | "regex-automata", 1405 | "regex-syntax", 1406 | ] 1407 | 1408 | [[package]] 1409 | name = "regex-automata" 1410 | version = "0.4.6" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 1413 | dependencies = [ 1414 | "aho-corasick", 1415 | "memchr", 1416 | "regex-syntax", 1417 | ] 1418 | 1419 | [[package]] 1420 | name = "regex-syntax" 1421 | version = "0.8.2" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 1424 | 1425 | [[package]] 1426 | name = "rustc-hash" 1427 | version = "1.1.0" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1430 | 1431 | [[package]] 1432 | name = "rustc_version" 1433 | version = "0.4.0" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1436 | dependencies = [ 1437 | "semver", 1438 | ] 1439 | 1440 | [[package]] 1441 | name = "rustversion" 1442 | version = "1.0.14" 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" 1444 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 1445 | 1446 | [[package]] 1447 | name = "ryu" 1448 | version = "1.0.17" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" 1451 | 1452 | [[package]] 1453 | name = "scopeguard" 1454 | version = "1.2.0" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1457 | 1458 | [[package]] 1459 | name = "semver" 1460 | version = "1.0.22" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" 1463 | 1464 | [[package]] 1465 | name = "serde" 1466 | version = "1.0.197" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" 1469 | dependencies = [ 1470 | "serde_derive", 1471 | ] 1472 | 1473 | [[package]] 1474 | name = "serde_bytes" 1475 | version = "0.11.14" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" 1478 | dependencies = [ 1479 | "serde", 1480 | ] 1481 | 1482 | [[package]] 1483 | name = "serde_derive" 1484 | version = "1.0.197" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" 1487 | dependencies = [ 1488 | "proc-macro2", 1489 | "quote", 1490 | "syn 2.0.52", 1491 | ] 1492 | 1493 | [[package]] 1494 | name = "serde_json" 1495 | version = "1.0.117" 1496 | source = "registry+https://github.com/rust-lang/crates.io-index" 1497 | checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" 1498 | dependencies = [ 1499 | "itoa", 1500 | "ryu", 1501 | "serde", 1502 | ] 1503 | 1504 | [[package]] 1505 | name = "serde_with" 1506 | version = "2.3.3" 1507 | source = "registry+https://github.com/rust-lang/crates.io-index" 1508 | checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" 1509 | dependencies = [ 1510 | "serde", 1511 | "serde_with_macros", 1512 | ] 1513 | 1514 | [[package]] 1515 | name = "serde_with_macros" 1516 | version = "2.3.3" 1517 | source = "registry+https://github.com/rust-lang/crates.io-index" 1518 | checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" 1519 | dependencies = [ 1520 | "darling", 1521 | "proc-macro2", 1522 | "quote", 1523 | "syn 2.0.52", 1524 | ] 1525 | 1526 | [[package]] 1527 | name = "sha2" 1528 | version = "0.9.9" 1529 | source = "registry+https://github.com/rust-lang/crates.io-index" 1530 | checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" 1531 | dependencies = [ 1532 | "block-buffer 0.9.0", 1533 | "cfg-if", 1534 | "cpufeatures", 1535 | "digest 0.9.0", 1536 | "opaque-debug", 1537 | ] 1538 | 1539 | [[package]] 1540 | name = "sha2" 1541 | version = "0.10.8" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1544 | dependencies = [ 1545 | "cfg-if", 1546 | "cpufeatures", 1547 | "digest 0.10.7", 1548 | ] 1549 | 1550 | [[package]] 1551 | name = "sha3" 1552 | version = "0.10.8" 1553 | source = "registry+https://github.com/rust-lang/crates.io-index" 1554 | checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" 1555 | dependencies = [ 1556 | "digest 0.10.7", 1557 | "keccak", 1558 | ] 1559 | 1560 | [[package]] 1561 | name = "signature" 1562 | version = "1.6.4" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" 1565 | 1566 | [[package]] 1567 | name = "siphasher" 1568 | version = "0.3.11" 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" 1570 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 1571 | 1572 | [[package]] 1573 | name = "sized-chunks" 1574 | version = "0.6.5" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" 1577 | dependencies = [ 1578 | "bitmaps", 1579 | "typenum", 1580 | ] 1581 | 1582 | [[package]] 1583 | name = "smallvec" 1584 | version = "1.13.1" 1585 | source = "registry+https://github.com/rust-lang/crates.io-index" 1586 | checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" 1587 | 1588 | [[package]] 1589 | name = "solana-frozen-abi" 1590 | version = "1.18.4" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "0e608aeeb42922e803589258af0028e2c9e70fbfb04e1f1ff6c5f07019c2af95" 1593 | dependencies = [ 1594 | "block-buffer 0.10.4", 1595 | "bs58", 1596 | "bv", 1597 | "either", 1598 | "generic-array", 1599 | "im", 1600 | "lazy_static", 1601 | "log", 1602 | "memmap2", 1603 | "rustc_version", 1604 | "serde", 1605 | "serde_bytes", 1606 | "serde_derive", 1607 | "sha2 0.10.8", 1608 | "solana-frozen-abi-macro", 1609 | "subtle", 1610 | "thiserror", 1611 | ] 1612 | 1613 | [[package]] 1614 | name = "solana-frozen-abi-macro" 1615 | version = "1.18.4" 1616 | source = "registry+https://github.com/rust-lang/crates.io-index" 1617 | checksum = "1cc7a55ad8a177287f3abb1be371b2a252d4c474d71c2e3983a7dff7db15d3f2" 1618 | dependencies = [ 1619 | "proc-macro2", 1620 | "quote", 1621 | "rustc_version", 1622 | "syn 2.0.52", 1623 | ] 1624 | 1625 | [[package]] 1626 | name = "solana-idl-classic" 1627 | version = "0.2.0" 1628 | source = "registry+https://github.com/rust-lang/crates.io-index" 1629 | checksum = "0c1d97756a955ebbe202c2b92c7cfb8ba85936de474edc7e733bd5f8a1f26070" 1630 | dependencies = [ 1631 | "serde", 1632 | ] 1633 | 1634 | [[package]] 1635 | name = "solana-idl-converter" 1636 | version = "0.2.0" 1637 | source = "registry+https://github.com/rust-lang/crates.io-index" 1638 | checksum = "4a1ae28868e1bfb26d542c636bc05163db7f04f17bdc5feda065eb32c8caab2a" 1639 | dependencies = [ 1640 | "anchor-lang-idl", 1641 | "serde", 1642 | "solana-idl-classic", 1643 | "thiserror", 1644 | ] 1645 | 1646 | [[package]] 1647 | name = "solana-logger" 1648 | version = "1.18.4" 1649 | source = "registry+https://github.com/rust-lang/crates.io-index" 1650 | checksum = "c2f507f00c12d7309475fde8bfc58502ed87bd4024769760223cc9c03dd5aa53" 1651 | dependencies = [ 1652 | "env_logger", 1653 | "lazy_static", 1654 | "log", 1655 | ] 1656 | 1657 | [[package]] 1658 | name = "solana-program" 1659 | version = "1.18.4" 1660 | source = "registry+https://github.com/rust-lang/crates.io-index" 1661 | checksum = "91fad730d66a6f33ef5bb180def74a3d84e7487b829a8f67ff58570332481f6c" 1662 | dependencies = [ 1663 | "ark-bn254", 1664 | "ark-ec", 1665 | "ark-ff", 1666 | "ark-serialize", 1667 | "base64 0.21.7", 1668 | "bincode", 1669 | "bitflags 2.4.2", 1670 | "blake3", 1671 | "borsh 0.10.3", 1672 | "borsh 0.9.3", 1673 | "borsh 1.3.1", 1674 | "bs58", 1675 | "bv", 1676 | "bytemuck", 1677 | "cc", 1678 | "console_error_panic_hook", 1679 | "console_log", 1680 | "curve25519-dalek", 1681 | "getrandom 0.2.12", 1682 | "itertools", 1683 | "js-sys", 1684 | "lazy_static", 1685 | "libc", 1686 | "libsecp256k1", 1687 | "light-poseidon", 1688 | "log", 1689 | "memoffset", 1690 | "num-bigint", 1691 | "num-derive", 1692 | "num-traits", 1693 | "parking_lot", 1694 | "rand 0.8.5", 1695 | "rustc_version", 1696 | "rustversion", 1697 | "serde", 1698 | "serde_bytes", 1699 | "serde_derive", 1700 | "serde_json", 1701 | "sha2 0.10.8", 1702 | "sha3", 1703 | "solana-frozen-abi", 1704 | "solana-frozen-abi-macro", 1705 | "solana-sdk-macro", 1706 | "thiserror", 1707 | "tiny-bip39", 1708 | "wasm-bindgen", 1709 | "zeroize", 1710 | ] 1711 | 1712 | [[package]] 1713 | name = "solana-sdk" 1714 | version = "1.18.4" 1715 | source = "registry+https://github.com/rust-lang/crates.io-index" 1716 | checksum = "6d3612d0b4c2120c2a4cef6bac8af469908575319249e1a5c0de5a487fb7610f" 1717 | dependencies = [ 1718 | "assert_matches", 1719 | "base64 0.21.7", 1720 | "bincode", 1721 | "bitflags 2.4.2", 1722 | "borsh 1.3.1", 1723 | "bs58", 1724 | "bytemuck", 1725 | "byteorder", 1726 | "chrono", 1727 | "derivation-path", 1728 | "digest 0.10.7", 1729 | "ed25519-dalek", 1730 | "ed25519-dalek-bip32", 1731 | "generic-array", 1732 | "hmac 0.12.1", 1733 | "itertools", 1734 | "js-sys", 1735 | "lazy_static", 1736 | "libsecp256k1", 1737 | "log", 1738 | "memmap2", 1739 | "num-derive", 1740 | "num-traits", 1741 | "num_enum", 1742 | "pbkdf2 0.11.0", 1743 | "qstring", 1744 | "qualifier_attr", 1745 | "rand 0.7.3", 1746 | "rand 0.8.5", 1747 | "rustc_version", 1748 | "rustversion", 1749 | "serde", 1750 | "serde_bytes", 1751 | "serde_derive", 1752 | "serde_json", 1753 | "serde_with", 1754 | "sha2 0.10.8", 1755 | "sha3", 1756 | "siphasher", 1757 | "solana-frozen-abi", 1758 | "solana-frozen-abi-macro", 1759 | "solana-logger", 1760 | "solana-program", 1761 | "solana-sdk-macro", 1762 | "thiserror", 1763 | "uriparse", 1764 | "wasm-bindgen", 1765 | ] 1766 | 1767 | [[package]] 1768 | name = "solana-sdk-macro" 1769 | version = "1.18.4" 1770 | source = "registry+https://github.com/rust-lang/crates.io-index" 1771 | checksum = "df952c230e35ba179882cef765655b171b8f99f512f04fc4a84800fa43cfe592" 1772 | dependencies = [ 1773 | "bs58", 1774 | "proc-macro2", 1775 | "quote", 1776 | "rustversion", 1777 | "syn 2.0.52", 1778 | ] 1779 | 1780 | [[package]] 1781 | name = "solana_idl" 1782 | version = "0.2.0" 1783 | source = "registry+https://github.com/rust-lang/crates.io-index" 1784 | checksum = "ae838dffc11f1858c80435dde65308685c3d8fc7cb8dfd9af08205a429907bf5" 1785 | dependencies = [ 1786 | "anchor-lang-idl", 1787 | "serde_json", 1788 | "solana-idl-classic", 1789 | "solana-idl-converter", 1790 | "thiserror", 1791 | ] 1792 | 1793 | [[package]] 1794 | name = "strsim" 1795 | version = "0.10.0" 1796 | source = "registry+https://github.com/rust-lang/crates.io-index" 1797 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1798 | 1799 | [[package]] 1800 | name = "subtle" 1801 | version = "2.5.0" 1802 | source = "registry+https://github.com/rust-lang/crates.io-index" 1803 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 1804 | 1805 | [[package]] 1806 | name = "syn" 1807 | version = "1.0.109" 1808 | source = "registry+https://github.com/rust-lang/crates.io-index" 1809 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1810 | dependencies = [ 1811 | "proc-macro2", 1812 | "quote", 1813 | "unicode-ident", 1814 | ] 1815 | 1816 | [[package]] 1817 | name = "syn" 1818 | version = "2.0.52" 1819 | source = "registry+https://github.com/rust-lang/crates.io-index" 1820 | checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" 1821 | dependencies = [ 1822 | "proc-macro2", 1823 | "quote", 1824 | "unicode-ident", 1825 | ] 1826 | 1827 | [[package]] 1828 | name = "syn_derive" 1829 | version = "0.1.8" 1830 | source = "registry+https://github.com/rust-lang/crates.io-index" 1831 | checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" 1832 | dependencies = [ 1833 | "proc-macro-error", 1834 | "proc-macro2", 1835 | "quote", 1836 | "syn 2.0.52", 1837 | ] 1838 | 1839 | [[package]] 1840 | name = "termcolor" 1841 | version = "1.4.1" 1842 | source = "registry+https://github.com/rust-lang/crates.io-index" 1843 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 1844 | dependencies = [ 1845 | "winapi-util", 1846 | ] 1847 | 1848 | [[package]] 1849 | name = "thiserror" 1850 | version = "1.0.61" 1851 | source = "registry+https://github.com/rust-lang/crates.io-index" 1852 | checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" 1853 | dependencies = [ 1854 | "thiserror-impl", 1855 | ] 1856 | 1857 | [[package]] 1858 | name = "thiserror-impl" 1859 | version = "1.0.61" 1860 | source = "registry+https://github.com/rust-lang/crates.io-index" 1861 | checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" 1862 | dependencies = [ 1863 | "proc-macro2", 1864 | "quote", 1865 | "syn 2.0.52", 1866 | ] 1867 | 1868 | [[package]] 1869 | name = "tiny-bip39" 1870 | version = "0.8.2" 1871 | source = "registry+https://github.com/rust-lang/crates.io-index" 1872 | checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" 1873 | dependencies = [ 1874 | "anyhow", 1875 | "hmac 0.8.1", 1876 | "once_cell", 1877 | "pbkdf2 0.4.0", 1878 | "rand 0.7.3", 1879 | "rustc-hash", 1880 | "sha2 0.9.9", 1881 | "thiserror", 1882 | "unicode-normalization", 1883 | "wasm-bindgen", 1884 | "zeroize", 1885 | ] 1886 | 1887 | [[package]] 1888 | name = "tinyvec" 1889 | version = "1.6.0" 1890 | source = "registry+https://github.com/rust-lang/crates.io-index" 1891 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1892 | dependencies = [ 1893 | "tinyvec_macros", 1894 | ] 1895 | 1896 | [[package]] 1897 | name = "tinyvec_macros" 1898 | version = "0.1.1" 1899 | source = "registry+https://github.com/rust-lang/crates.io-index" 1900 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1901 | 1902 | [[package]] 1903 | name = "toml" 1904 | version = "0.5.11" 1905 | source = "registry+https://github.com/rust-lang/crates.io-index" 1906 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 1907 | dependencies = [ 1908 | "serde", 1909 | ] 1910 | 1911 | [[package]] 1912 | name = "toml_datetime" 1913 | version = "0.6.5" 1914 | source = "registry+https://github.com/rust-lang/crates.io-index" 1915 | checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" 1916 | 1917 | [[package]] 1918 | name = "toml_edit" 1919 | version = "0.21.1" 1920 | source = "registry+https://github.com/rust-lang/crates.io-index" 1921 | checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" 1922 | dependencies = [ 1923 | "indexmap", 1924 | "toml_datetime", 1925 | "winnow", 1926 | ] 1927 | 1928 | [[package]] 1929 | name = "typenum" 1930 | version = "1.17.0" 1931 | source = "registry+https://github.com/rust-lang/crates.io-index" 1932 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1933 | 1934 | [[package]] 1935 | name = "unicode-ident" 1936 | version = "1.0.12" 1937 | source = "registry+https://github.com/rust-lang/crates.io-index" 1938 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1939 | 1940 | [[package]] 1941 | name = "unicode-normalization" 1942 | version = "0.1.23" 1943 | source = "registry+https://github.com/rust-lang/crates.io-index" 1944 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 1945 | dependencies = [ 1946 | "tinyvec", 1947 | ] 1948 | 1949 | [[package]] 1950 | name = "uriparse" 1951 | version = "0.6.4" 1952 | source = "registry+https://github.com/rust-lang/crates.io-index" 1953 | checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" 1954 | dependencies = [ 1955 | "fnv", 1956 | "lazy_static", 1957 | ] 1958 | 1959 | [[package]] 1960 | name = "version_check" 1961 | version = "0.9.4" 1962 | source = "registry+https://github.com/rust-lang/crates.io-index" 1963 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1964 | 1965 | [[package]] 1966 | name = "wasi" 1967 | version = "0.9.0+wasi-snapshot-preview1" 1968 | source = "registry+https://github.com/rust-lang/crates.io-index" 1969 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1970 | 1971 | [[package]] 1972 | name = "wasi" 1973 | version = "0.11.0+wasi-snapshot-preview1" 1974 | source = "registry+https://github.com/rust-lang/crates.io-index" 1975 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1976 | 1977 | [[package]] 1978 | name = "wasm-bindgen" 1979 | version = "0.2.92" 1980 | source = "registry+https://github.com/rust-lang/crates.io-index" 1981 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 1982 | dependencies = [ 1983 | "cfg-if", 1984 | "wasm-bindgen-macro", 1985 | ] 1986 | 1987 | [[package]] 1988 | name = "wasm-bindgen-backend" 1989 | version = "0.2.92" 1990 | source = "registry+https://github.com/rust-lang/crates.io-index" 1991 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 1992 | dependencies = [ 1993 | "bumpalo", 1994 | "log", 1995 | "once_cell", 1996 | "proc-macro2", 1997 | "quote", 1998 | "syn 2.0.52", 1999 | "wasm-bindgen-shared", 2000 | ] 2001 | 2002 | [[package]] 2003 | name = "wasm-bindgen-macro" 2004 | version = "0.2.92" 2005 | source = "registry+https://github.com/rust-lang/crates.io-index" 2006 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 2007 | dependencies = [ 2008 | "quote", 2009 | "wasm-bindgen-macro-support", 2010 | ] 2011 | 2012 | [[package]] 2013 | name = "wasm-bindgen-macro-support" 2014 | version = "0.2.92" 2015 | source = "registry+https://github.com/rust-lang/crates.io-index" 2016 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 2017 | dependencies = [ 2018 | "proc-macro2", 2019 | "quote", 2020 | "syn 2.0.52", 2021 | "wasm-bindgen-backend", 2022 | "wasm-bindgen-shared", 2023 | ] 2024 | 2025 | [[package]] 2026 | name = "wasm-bindgen-shared" 2027 | version = "0.2.92" 2028 | source = "registry+https://github.com/rust-lang/crates.io-index" 2029 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 2030 | 2031 | [[package]] 2032 | name = "web-sys" 2033 | version = "0.3.69" 2034 | source = "registry+https://github.com/rust-lang/crates.io-index" 2035 | checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 2036 | dependencies = [ 2037 | "js-sys", 2038 | "wasm-bindgen", 2039 | ] 2040 | 2041 | [[package]] 2042 | name = "winapi" 2043 | version = "0.3.9" 2044 | source = "registry+https://github.com/rust-lang/crates.io-index" 2045 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2046 | dependencies = [ 2047 | "winapi-i686-pc-windows-gnu", 2048 | "winapi-x86_64-pc-windows-gnu", 2049 | ] 2050 | 2051 | [[package]] 2052 | name = "winapi-i686-pc-windows-gnu" 2053 | version = "0.4.0" 2054 | source = "registry+https://github.com/rust-lang/crates.io-index" 2055 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2056 | 2057 | [[package]] 2058 | name = "winapi-util" 2059 | version = "0.1.6" 2060 | source = "registry+https://github.com/rust-lang/crates.io-index" 2061 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 2062 | dependencies = [ 2063 | "winapi", 2064 | ] 2065 | 2066 | [[package]] 2067 | name = "winapi-x86_64-pc-windows-gnu" 2068 | version = "0.4.0" 2069 | source = "registry+https://github.com/rust-lang/crates.io-index" 2070 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2071 | 2072 | [[package]] 2073 | name = "windows-targets" 2074 | version = "0.48.5" 2075 | source = "registry+https://github.com/rust-lang/crates.io-index" 2076 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2077 | dependencies = [ 2078 | "windows_aarch64_gnullvm", 2079 | "windows_aarch64_msvc", 2080 | "windows_i686_gnu", 2081 | "windows_i686_msvc", 2082 | "windows_x86_64_gnu", 2083 | "windows_x86_64_gnullvm", 2084 | "windows_x86_64_msvc", 2085 | ] 2086 | 2087 | [[package]] 2088 | name = "windows_aarch64_gnullvm" 2089 | version = "0.48.5" 2090 | source = "registry+https://github.com/rust-lang/crates.io-index" 2091 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2092 | 2093 | [[package]] 2094 | name = "windows_aarch64_msvc" 2095 | version = "0.48.5" 2096 | source = "registry+https://github.com/rust-lang/crates.io-index" 2097 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2098 | 2099 | [[package]] 2100 | name = "windows_i686_gnu" 2101 | version = "0.48.5" 2102 | source = "registry+https://github.com/rust-lang/crates.io-index" 2103 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2104 | 2105 | [[package]] 2106 | name = "windows_i686_msvc" 2107 | version = "0.48.5" 2108 | source = "registry+https://github.com/rust-lang/crates.io-index" 2109 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2110 | 2111 | [[package]] 2112 | name = "windows_x86_64_gnu" 2113 | version = "0.48.5" 2114 | source = "registry+https://github.com/rust-lang/crates.io-index" 2115 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2116 | 2117 | [[package]] 2118 | name = "windows_x86_64_gnullvm" 2119 | version = "0.48.5" 2120 | source = "registry+https://github.com/rust-lang/crates.io-index" 2121 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2122 | 2123 | [[package]] 2124 | name = "windows_x86_64_msvc" 2125 | version = "0.48.5" 2126 | source = "registry+https://github.com/rust-lang/crates.io-index" 2127 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2128 | 2129 | [[package]] 2130 | name = "winnow" 2131 | version = "0.5.40" 2132 | source = "registry+https://github.com/rust-lang/crates.io-index" 2133 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 2134 | dependencies = [ 2135 | "memchr", 2136 | ] 2137 | 2138 | [[package]] 2139 | name = "zerocopy" 2140 | version = "0.7.32" 2141 | source = "registry+https://github.com/rust-lang/crates.io-index" 2142 | checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" 2143 | dependencies = [ 2144 | "zerocopy-derive", 2145 | ] 2146 | 2147 | [[package]] 2148 | name = "zerocopy-derive" 2149 | version = "0.7.32" 2150 | source = "registry+https://github.com/rust-lang/crates.io-index" 2151 | checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" 2152 | dependencies = [ 2153 | "proc-macro2", 2154 | "quote", 2155 | "syn 2.0.52", 2156 | ] 2157 | 2158 | [[package]] 2159 | name = "zeroize" 2160 | version = "1.3.0" 2161 | source = "registry+https://github.com/rust-lang/crates.io-index" 2162 | checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" 2163 | dependencies = [ 2164 | "zeroize_derive", 2165 | ] 2166 | 2167 | [[package]] 2168 | name = "zeroize_derive" 2169 | version = "1.4.2" 2170 | source = "registry+https://github.com/rust-lang/crates.io-index" 2171 | checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" 2172 | dependencies = [ 2173 | "proc-macro2", 2174 | "quote", 2175 | "syn 2.0.52", 2176 | ] 2177 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chainparser" 3 | description = "Parses Solana account data using provided IDL" 4 | version = "0.3.0" 5 | edition = "2021" 6 | repository = "https://github.com/thlorenz/chainparser" 7 | readme = "README.md" 8 | license = "MIT" 9 | 10 | [dependencies] 11 | arrayref = "0.3.7" 12 | borsh = "0.9.3" 13 | flate2 = "1.0.28" 14 | heck = "0.5.0" 15 | lazy_static = "1.5.0" 16 | log = "0.4.21" 17 | serde = "1.0.197" 18 | serde_json = "1.0.117" 19 | solana-sdk = "1.18.4" 20 | solana_idl = "0.2.0" 21 | thiserror = "1.0.57" 22 | 23 | [dev-dependencies] 24 | base64 = "0.22.0" 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Thorsten Lorenz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # directory of this makefile 2 | DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 3 | 4 | CARGO_TEST=nextest run 5 | CARGO_TEST_NOCAP=nextest run --nocapture 6 | $(if $(shell command -v cargo-nextest 2> /dev/null),,$(eval CARGO_TEST=test)) 7 | $(if $(shell command -v cargo-nextest 2> /dev/null),,$(eval CARGO_TEST_NOCAP=test -- --nocapture)) 8 | 9 | test: 10 | cargo $(CARGO_TEST) 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chainparser [![chainparser Build+Test](https://github.com/thlorenz/chainparser/actions/workflows/build-test-lint.yml/badge.svg)](https://github.com/thlorenz/chainparser/actions/workflows/build-test-lint.yml) 2 | 3 | Deserializing Solana accounts using their progam IDL 4 | 5 | ```rs 6 | use chainparser::{ChainparserDeserializer, IdlProvider, SerializationOpts}; 7 | 8 | let opts = SerializationOpts { 9 | pubkey_as_base58: true, 10 | n64_as_string: false, 11 | n128_as_string: true, 12 | }; 13 | 14 | let mut chainparser = ChainparserDeserializer::new(&opts); 15 | 16 | // 1. Add IDLS 17 | 18 | // Candy Machine IDL 19 | let cndy_program_id = "cndy3Z4yapfJBmL3ShUp5exZKqR3z33thTzeNMm2gRZ"; 20 | { 21 | let idl_json = read_idl_json(&cndy_program_id); 22 | chainparser 23 | .add_idl_json(cndy_program_id.to_string(), &idl_json, IdlProvider::Anchor) 24 | .expect("failed adding IDL JSON"); 25 | } 26 | 27 | // Staking Program IDL 28 | let stake_program_id = "StakeSSzfxn391k3LvdKbZP5WVwWd6AsY1DNiXHjQfK"; 29 | { 30 | let idl_json = read_idl_json(&stake_program_id); 31 | chainparser 32 | .add_idl_json(stake_program_id.to_string(), &idl_json, IdlProvider::Anchor) 33 | .expect("failed adding IDL JSON"); 34 | } 35 | 36 | // 2. Read Accounts Data 37 | 38 | // Stake Account 39 | let stake_acc_data = read_account( 40 | &stake_program_id, 41 | "EscrowHistory", 42 | "5AEHnKRonYWeXWQTCqbfaEY6jHy38ifutWsriVsxsgbL", 43 | ); 44 | 45 | // Candy Machine Account 46 | let cndy_acc_data = read_account( 47 | &cndy_program_id, 48 | "CollectionPDA", 49 | "4gt6YPtgZp2MYJUP7cAH8E3UiL6mUruYaPprEiyJytQ4", 50 | ); 51 | 52 | // 3. Deserialize Accounts 53 | 54 | // Stake Account 55 | { 56 | let mut acc_json = String::new(); 57 | chainparser 58 | .deserialize_account( 59 | &stake_program_id, 60 | &mut stake_acc_data.as_slice(), 61 | &mut acc_json, 62 | ) 63 | .expect("failed to deserialize account"); 64 | assert!(acc_json.contains("{\"escrow\":\"4uj6fRJzqoNRPktmYqGX1nBkjAJBsimJ4ug77S3Tzj7y\"")); 65 | } 66 | 67 | // Candy Machine Account 68 | { 69 | let mut acc_json = String::new(); 70 | chainparser 71 | .deserialize_account( 72 | &cndy_program_id, 73 | &mut cndy_acc_data.as_slice(), 74 | &mut acc_json, 75 | ) 76 | .expect("failed to deserialize account"); 77 | assert_eq!(acc_json, "{\"mint\":\"BrqNo3sQFTaq9JevoWYhgagJEjE3MmTgYonfaHV5Mf3E\",\"candyMachine\":\"DpBwktkJsEPTtsRpD8kCFGwEUjwTkXARSGSTQ7MJr4kE\"}"); 78 | } 79 | ``` 80 | 81 | ## LICENSE 82 | 83 | MIT 84 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-commit-message = "chore: release v{{version}}" 2 | tag-message = "{{tag_name}}" 3 | tag-name = "{{crate_name}}@v{{version}}" 4 | consolidate-commits = true 5 | allow-branch = ["master"] 6 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | imports_granularity = "Crate" 3 | max_width = 80 4 | -------------------------------------------------------------------------------- /src/api.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | pub use std::fmt::Write; 3 | 4 | use solana_idl::Idl; 5 | use solana_sdk::pubkey::Pubkey; 6 | 7 | pub use crate::json::{JsonAccountsDeserializer, JsonSerializationOpts}; 8 | use crate::{ 9 | deserializer::DeserializeProvider, 10 | errors::{ChainparserError, ChainparserResult}, 11 | idl::{try_find_idl_for_program, IdlProvider, IDL_PROVIDERS}, 12 | traits::AccountProvider, 13 | }; 14 | 15 | /// Setup to deserialize accounts for a given program. The accounts are expected to have been 16 | /// serialized using the [borsh] format. 17 | /// 18 | /// Uses deserializers defined inside [deserializer] modules under the hood in order to resolve the 19 | /// appropriate [borsh] deserializers for each field. 20 | pub struct ChainparserDeserializer<'opts> { 21 | /// The deserializers for accounts of for each program 22 | json_account_deserializers: 23 | HashMap>, 24 | 25 | /// The [JsonSerializationOpts] specifying how specific data types should be deserialized. 26 | json_serialization_opts: &'opts JsonSerializationOpts, 27 | } 28 | 29 | impl<'opts> ChainparserDeserializer<'opts> { 30 | /// Creates an instance of a [ChainparserDeserializer]. 31 | /// Make sure to use [ChainparserDeserializer::add_idl_json] for each program _before_ attempting 32 | /// to deserialize accounts for it. 33 | /// 34 | /// - [serialization_opts] specifying how specific data types should be deserialized. 35 | pub fn new(json_serialization_opts: &'opts JsonSerializationOpts) -> Self { 36 | Self { 37 | json_account_deserializers: HashMap::new(), 38 | json_serialization_opts, 39 | } 40 | } 41 | 42 | /// Attempts to find the IDL account for the given [program_id] and adds it to the 43 | /// deserializer. 44 | /// It first tries to find an anchor IDl account and then tries shank. 45 | /// Returns [Some::] if the IDL was found and added, and [None::] if 46 | /// neither a shank nor an anchor IDL account was found. 47 | pub fn try_add_idl_for_program( 48 | &mut self, 49 | account_provider: &T, 50 | program_id: &Pubkey, 51 | ) -> ChainparserResult> { 52 | for idl_provider in IDL_PROVIDERS { 53 | if let Some(idl) = try_find_idl_for_program( 54 | account_provider, 55 | program_id, 56 | idl_provider, 57 | )? { 58 | self.add_idl( 59 | program_id.to_string(), 60 | idl, 61 | idl_provider.clone(), 62 | )?; 63 | return Ok(Some(idl_provider.clone())); 64 | } 65 | } 66 | Ok(None) 67 | } 68 | 69 | /// Parses an [IDL] specification from the provided [idl_json] for the [id] and adds a 70 | /// json accounts deserializer derived from it. 71 | /// The id is usually the program id, possibly combined with the slot at which the IDL was 72 | /// uploaded. 73 | pub fn add_idl_json( 74 | &mut self, 75 | id: String, 76 | idl_json: &str, 77 | provider: IdlProvider, 78 | ) -> ChainparserResult<()> { 79 | let json_deserializer = JsonAccountsDeserializer::try_from_idl( 80 | idl_json, 81 | provider, 82 | self.json_serialization_opts, 83 | )?; 84 | self.json_account_deserializers 85 | .insert(id, json_deserializer); 86 | Ok(()) 87 | } 88 | 89 | /// Adds [IDL] specification from the provided [idl] for the [id] and adds a 90 | /// json accounts deserializer derived from it. 91 | /// The id is usually the program id, possibly combined with the slot at which the IDL was 92 | /// uploaded. 93 | pub fn add_idl( 94 | &mut self, 95 | id: String, 96 | idl: Idl, 97 | provider: IdlProvider, 98 | ) -> ChainparserResult<()> { 99 | let de_provider = DeserializeProvider::try_from(&idl)?; 100 | 101 | let json_deserializer = JsonAccountsDeserializer::from_idl( 102 | &idl, 103 | de_provider, 104 | provider, 105 | self.json_serialization_opts, 106 | ); 107 | self.json_account_deserializers 108 | .insert(id, json_deserializer); 109 | Ok(()) 110 | } 111 | 112 | pub fn account_name(&self, id: &str, account_data: &[u8]) -> Option<&str> { 113 | self.json_account_deserializers 114 | .get(id) 115 | .and_then(|deserializer| deserializer.account_name(account_data)) 116 | } 117 | 118 | /// Returns `true` if the IDL of the given [id] has been added to the deserializer. 119 | /// The id is usually the program id, possibly combined with the slot at which the IDL was 120 | /// uploaded. 121 | pub fn has_idl(&self, id: &str) -> bool { 122 | self.json_account_deserializers.contains_key(id) 123 | } 124 | 125 | /// Returns all program ids for which IDLs have been added to the deserializer. 126 | pub fn added_idls(&self) -> HashSet { 127 | self.json_account_deserializers.keys().cloned().collect() 128 | } 129 | 130 | /// Deserializes an account to a JSON string. 131 | /// 132 | /// In order to specify a custom [Write] writer, i.e. a socket connection to write to, use 133 | /// [deserialize_account] instead. 134 | /// 135 | /// - [id] is the program id of program that owns the account, possibly combined with the slot 136 | /// at which the IDL to use for deserialization was uploaded. 137 | /// make sure to add it's IDL before via [ChainparserDeserializer::add_idl_json]. 138 | /// - [account_data] is the raw account data as a byte array 139 | pub fn deserialize_account_to_json_string( 140 | &self, 141 | id: &str, 142 | account_data: &mut &[u8], 143 | ) -> ChainparserResult { 144 | let mut f = String::new(); 145 | self.deserialize_account_to_json(id, account_data, &mut f)?; 146 | Ok(f) 147 | } 148 | 149 | /// Deserializes an account and writes the resulting JSON to the provided [Write] write [f]. 150 | /// 151 | /// - [id] is the program id of program that owns the account, possibly combined with the slot 152 | /// at which the IDL to use for deserialization was uploaded. Make sure to add it's IDL before 153 | /// via [ChainparserDeserializer::add_idl_json]. 154 | /// - [account_data] is the raw account data as a byte array 155 | /// - [f] is the [Write] writer to write the resulting JSON to, i.e. `std::io::stdout()` or 156 | /// `String::new()` 157 | pub fn deserialize_account_to_json( 158 | &self, 159 | id: &str, 160 | account_data: &mut &[u8], 161 | f: &mut W, 162 | ) -> ChainparserResult<()> { 163 | let deserializer = 164 | self.json_account_deserializers.get(id).ok_or_else(|| { 165 | ChainparserError::CannotFindAccountDeserializerForProgramId( 166 | id.to_string(), 167 | ) 168 | })?; 169 | 170 | deserializer.deserialize_account_data(account_data, f)?; 171 | Ok(()) 172 | } 173 | 174 | pub fn deserialize_account_to_json_by_name( 175 | &self, 176 | id: &str, 177 | name: &str, 178 | account_data: &mut &[u8], 179 | f: &mut W, 180 | ) -> ChainparserResult<()> { 181 | let deserializer = 182 | self.json_account_deserializers.get(id).ok_or_else(|| { 183 | ChainparserError::CannotFindAccountDeserializerForProgramId( 184 | id.to_string(), 185 | ) 186 | })?; 187 | 188 | deserializer.deserialize_account_data_by_name(account_data, name, f)?; 189 | Ok(()) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/de.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use serde::{de::Error, Deserialize, Deserializer}; 4 | use solana_sdk::pubkey::Pubkey; 5 | 6 | /// Function to provide to [serde] in order to deserialize a [Pubkey] from a base58 string. 7 | /// Use as follows: `#[serde(deserialize_with = "pubkey_from_base58")]` 8 | pub fn pubkey_from_base58<'de, D>(deserializer: D) -> Result 9 | where 10 | D: Deserializer<'de>, 11 | { 12 | let s: &str = Deserialize::deserialize(deserializer)?; 13 | Pubkey::from_str(s).map_err(D::Error::custom) 14 | } 15 | 16 | /// Function to provide to [serde] in order to deserialize a [Vec] from a a vec of base58 17 | /// strings. 18 | /// Use as follows: `#[serde(deserialize_with = "vec_pubkey_from_base58")]` 19 | pub fn vec_pubkey_from_base58<'de, D>( 20 | deserializer: D, 21 | ) -> Result, D::Error> 22 | where 23 | D: Deserializer<'de>, 24 | { 25 | let xs: Vec<&str> = Deserialize::deserialize(deserializer)?; 26 | xs.into_iter() 27 | .map(|s| Pubkey::from_str(s).map_err(D::Error::custom)) 28 | .collect() 29 | } 30 | 31 | /// Function to provide to [serde] in order to deserialize a [Option] from a base58 string 32 | /// option. 33 | /// Use as follows: `#[serde(deserialize_with = "opt_pubkey_from_base58")]` 34 | pub fn opt_pubkey_from_base58<'de, D>( 35 | deserializer: D, 36 | ) -> Result, D::Error> 37 | where 38 | D: Deserializer<'de>, 39 | { 40 | let opt: Option<&str> = Deserialize::deserialize(deserializer)?; 41 | opt.map(|s| Pubkey::from_str(s).map_err(D::Error::custom)) 42 | .transpose() 43 | } 44 | 45 | /// Function to provide to [serde] in order to deserialize a [u64] from a string. 46 | /// Use as follows: `#[serde(deserialize_with = "u64_from_string")]` 47 | pub fn u64_from_string<'de, D>(deserializer: D) -> Result 48 | where 49 | D: Deserializer<'de>, 50 | { 51 | let s: &str = Deserialize::deserialize(deserializer)?; 52 | u64::from_str(s).map_err(D::Error::custom) 53 | } 54 | 55 | /// Function to provide to [serde] in order to deserialize a [i64] from a string. 56 | /// Use as follows: `#[serde(deserialize_with = "i64_from_string")]` 57 | pub fn i64_from_string<'de, D>(deserializer: D) -> Result 58 | where 59 | D: Deserializer<'de>, 60 | { 61 | let s: &str = Deserialize::deserialize(deserializer)?; 62 | i64::from_str(s).map_err(D::Error::custom) 63 | } 64 | 65 | /// Function to provide to [serde] in order to deserialize a [u128] from a string. 66 | /// Use as follows: `#[serde(deserialize_with = "u128_from_string")]` 67 | pub fn u128_from_string<'de, D>(deserializer: D) -> Result 68 | where 69 | D: Deserializer<'de>, 70 | { 71 | let s: &str = Deserialize::deserialize(deserializer)?; 72 | u128::from_str(s).map_err(D::Error::custom) 73 | } 74 | 75 | /// Function to provide to [serde] in order to deserialize a [i128] from a string. 76 | /// Use as follows: `#[serde(deserialize_with = "i128_from_string")]` 77 | pub fn i128_from_string<'de, D>(deserializer: D) -> Result 78 | where 79 | D: Deserializer<'de>, 80 | { 81 | let s: &str = Deserialize::deserialize(deserializer)?; 82 | i128::from_str(s).map_err(D::Error::custom) 83 | } 84 | -------------------------------------------------------------------------------- /src/deserializer/borsh.rs: -------------------------------------------------------------------------------- 1 | use borsh::BorshDeserialize; 2 | use solana_idl::IdlType; 3 | use solana_sdk::pubkey::Pubkey; 4 | 5 | use super::{ 6 | floats::{deserialize_f32, deserialize_f64}, 7 | ChainparserDeserialize, 8 | }; 9 | use crate::errors::{ChainparserError, ChainparserResult as Result}; 10 | 11 | #[derive(Clone, Copy)] 12 | pub struct BorshDeserializer; 13 | 14 | impl ChainparserDeserialize for BorshDeserializer { 15 | fn u8(&self, buf: &mut &[u8]) -> Result { 16 | u8::deserialize(buf).map_err(|e| { 17 | ChainparserError::BorshDeserializeTypeError( 18 | "u8".to_string(), 19 | e, 20 | buf.to_vec(), 21 | ) 22 | }) 23 | } 24 | 25 | fn u16(&self, buf: &mut &[u8]) -> Result { 26 | u16::deserialize(buf).map_err(|e| { 27 | ChainparserError::BorshDeserializeTypeError( 28 | "u16".to_string(), 29 | e, 30 | buf.to_vec(), 31 | ) 32 | }) 33 | } 34 | 35 | fn u32(&self, buf: &mut &[u8]) -> Result { 36 | u32::deserialize(buf).map_err(|e| { 37 | ChainparserError::BorshDeserializeTypeError( 38 | "u32".to_string(), 39 | e, 40 | buf.to_vec(), 41 | ) 42 | }) 43 | } 44 | 45 | fn u64(&self, buf: &mut &[u8]) -> Result { 46 | u64::deserialize(buf).map_err(|e| { 47 | ChainparserError::BorshDeserializeTypeError( 48 | "u64".to_string(), 49 | e, 50 | buf.to_vec(), 51 | ) 52 | }) 53 | } 54 | 55 | fn u128(&self, buf: &mut &[u8]) -> Result { 56 | u128::deserialize(buf).map_err(|e| { 57 | ChainparserError::BorshDeserializeTypeError( 58 | "u128".to_string(), 59 | e, 60 | buf.to_vec(), 61 | ) 62 | }) 63 | } 64 | 65 | fn i8(&self, buf: &mut &[u8]) -> Result { 66 | i8::deserialize(buf).map_err(|e| { 67 | ChainparserError::BorshDeserializeTypeError( 68 | "i8".to_string(), 69 | e, 70 | buf.to_vec(), 71 | ) 72 | }) 73 | } 74 | 75 | fn i16(&self, buf: &mut &[u8]) -> Result { 76 | i16::deserialize(buf).map_err(|e| { 77 | ChainparserError::BorshDeserializeTypeError( 78 | "i16".to_string(), 79 | e, 80 | buf.to_vec(), 81 | ) 82 | }) 83 | } 84 | 85 | fn i32(&self, buf: &mut &[u8]) -> Result { 86 | i32::deserialize(buf).map_err(|e| { 87 | ChainparserError::BorshDeserializeTypeError( 88 | "i32".to_string(), 89 | e, 90 | buf.to_vec(), 91 | ) 92 | }) 93 | } 94 | 95 | fn i64(&self, buf: &mut &[u8]) -> Result { 96 | i64::deserialize(buf).map_err(|e| { 97 | ChainparserError::BorshDeserializeTypeError( 98 | "i64".to_string(), 99 | e, 100 | buf.to_vec(), 101 | ) 102 | }) 103 | } 104 | 105 | fn i128(&self, buf: &mut &[u8]) -> Result { 106 | i128::deserialize(buf).map_err(|e| { 107 | ChainparserError::BorshDeserializeTypeError( 108 | "i128".to_string(), 109 | e, 110 | buf.to_vec(), 111 | ) 112 | }) 113 | } 114 | 115 | fn f32(&self, buf: &mut &[u8]) -> Result { 116 | deserialize_f32(buf) 117 | } 118 | 119 | fn f64(&self, buf: &mut &[u8]) -> Result { 120 | deserialize_f64(buf) 121 | } 122 | 123 | fn bool(&self, buf: &mut &[u8]) -> Result { 124 | bool::deserialize(buf).map_err(|e| { 125 | ChainparserError::BorshDeserializeTypeError( 126 | "bool".to_string(), 127 | e, 128 | buf.to_vec(), 129 | ) 130 | }) 131 | } 132 | 133 | fn string(&self, buf: &mut &[u8]) -> Result { 134 | String::deserialize(buf).map_err(|e| { 135 | ChainparserError::BorshDeserializeTypeError( 136 | "String".to_string(), 137 | e, 138 | buf.to_vec(), 139 | ) 140 | }) 141 | } 142 | 143 | fn bytes(&self, buf: &mut &[u8]) -> Result> { 144 | Vec::::deserialize(buf).map_err(|e| { 145 | ChainparserError::BorshDeserializeTypeError( 146 | "bytes".to_string(), 147 | e, 148 | buf.to_vec(), 149 | ) 150 | }) 151 | } 152 | 153 | fn pubkey(&self, buf: &mut &[u8]) -> Result { 154 | Pubkey::deserialize(buf).map_err(|e| { 155 | ChainparserError::BorshDeserializeTypeError( 156 | "Pubkey".to_string(), 157 | e, 158 | buf.to_vec(), 159 | ) 160 | }) 161 | } 162 | 163 | fn option(&self, buf: &mut &[u8]) -> Result { 164 | self.u8(buf).map(|v| v != 0) 165 | } 166 | 167 | fn coption(&self, _buf: &mut &[u8], _inner: &IdlType) -> Result { 168 | Err(ChainparserError::DeserializerDoesNotSupportType( 169 | "borsh".to_string(), 170 | "coption".to_string(), 171 | )) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/deserializer/floats.rs: -------------------------------------------------------------------------------- 1 | use borsh::BorshDeserialize; 2 | 3 | use crate::errors::{ChainparserError, ChainparserResult}; 4 | 5 | // Floats we saw in the logs for accounts of '4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg' 6 | // that borsh considered NaNs: 7 | // 8 | // [ 79, 103, 129, 255] 9 | // [ 85, 255, 255, 255] 10 | // [ 91, 62, 255, 255] 11 | // [ 97, 176, 250, 255] 12 | // [120, 254, 255, 255] 13 | // [131, 253, 255, 255] 14 | // [153, 165, 247, 255] 15 | // [159, 203, 152, 255] 16 | // [184, 89, 253, 255] 17 | // [255, 255, 255, 255] 18 | 19 | // However via tests I discovered that borsh doesn't handle `127` (0x7F) as last byte well either. 20 | // Thus if the last 7 bits of the last byte are set then that denotes NaN 21 | 22 | // For f64 the second to last byte has to have the first 4 bits set and the last byte has 23 | // to be have the last 7 set to be considered NaN. 24 | 25 | const LOWER7_BITS_MASK: u8 = 0b0111_1111; 26 | const UPPER4_BITS_MASK: u8 = 0b1111_0000; 27 | 28 | /// Deserializes f32 supporting NAN by identifying them instead of using borsh which 29 | /// does not support NAN and errors instead. 30 | pub fn deserialize_f32(buf: &mut &[u8]) -> ChainparserResult { 31 | if buf.len() >= 4 { 32 | let f32_slice = [buf[0], buf[1], buf[2], buf[3]]; 33 | if (buf[3] & LOWER7_BITS_MASK) == LOWER7_BITS_MASK { 34 | *buf = &buf[4..]; 35 | Ok(f32::NAN) 36 | } else { 37 | f32::deserialize(buf).map_err(|e| { 38 | ChainparserError::BorshDeserializeFloatError( 39 | "f32".to_string(), 40 | e, 41 | f32_slice.to_vec(), 42 | ) 43 | }) 44 | } 45 | } else { 46 | f32::deserialize(buf).map_err(|e| { 47 | ChainparserError::BorshDeserializeFloatError( 48 | "f32".to_string(), 49 | e, 50 | buf.to_vec(), 51 | ) 52 | }) 53 | } 54 | } 55 | 56 | /// Deserializes f64 supporting NAN by identifying them instead of using borsh which 57 | /// does not support NAN and errors instead. 58 | pub fn deserialize_f64(buf: &mut &[u8]) -> ChainparserResult { 59 | if buf.len() >= 8 { 60 | let f64_slice = [ 61 | buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], 62 | ]; 63 | if (buf[6] & UPPER4_BITS_MASK) == UPPER4_BITS_MASK 64 | && (buf[7] & LOWER7_BITS_MASK) == LOWER7_BITS_MASK 65 | { 66 | *buf = &buf[8..]; 67 | Ok(f64::NAN) 68 | } else { 69 | f64::deserialize(buf).map_err(|e| { 70 | ChainparserError::BorshDeserializeFloatError( 71 | "f64".to_string(), 72 | e, 73 | f64_slice.to_vec(), 74 | ) 75 | }) 76 | } 77 | } else { 78 | f64::deserialize(buf).map_err(|e| { 79 | ChainparserError::BorshDeserializeFloatError( 80 | "f64".to_string(), 81 | e, 82 | buf.to_vec(), 83 | ) 84 | }) 85 | } 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | use super::*; 91 | 92 | #[test] 93 | fn f32_nan() { 94 | let cases = vec![ 95 | [79, 103, 129, 0b1111_1111], 96 | [0, 0, 192, 0b0111_1111], 97 | [0, 0, 0, 0b0111_1111], 98 | [0, 0, 0, 0b1111_1111], 99 | ]; 100 | for case in cases { 101 | let buf = case.to_vec(); 102 | let res = deserialize_f32(&mut &buf[..]); 103 | assert!(res.unwrap().is_nan()); 104 | } 105 | } 106 | 107 | #[test] 108 | fn f32_not_nan() { 109 | let cases = vec![ 110 | [79, 103, 129, 0b1111_1110], 111 | [0, 0, 192, 0b0111_1101], 112 | [0, 0, 0, 0b0111_1101], 113 | [0, 0, 0, 0b1011_1111], 114 | ]; 115 | for case in cases { 116 | let buf = case.to_vec(); 117 | let res = deserialize_f32(&mut &buf[..]); 118 | assert!(!res.unwrap().is_nan()); 119 | } 120 | } 121 | 122 | #[test] 123 | fn f64_nan() { 124 | let cases = vec![ 125 | [100, 0, 0, 0, 79, 103, 0b1111_1111, 0b1111_1111], 126 | [100, 0, 0, 0, 79, 103, 0b1111_1111, 0b0111_1111], 127 | [100, 0, 0, 0, 79, 103, 0b1111_1000, 0b0111_1111], 128 | [100, 0, 0, 0, 79, 103, 0b1111_0000, 0b0111_1111], 129 | [100, 0, 0, 0, 79, 103, 0b1111_0001, 0b0111_1111], 130 | [100, 0, 0, 0, 79, 103, 0b1111_0101, 0b0111_1111], 131 | [100, 0, 0, 0, 79, 103, 0b1111_1101, 0b0111_1111], 132 | ]; 133 | for case in cases { 134 | let buf = case.to_vec(); 135 | let res = deserialize_f64(&mut &buf[..]); 136 | assert!(res.unwrap().is_nan()); 137 | } 138 | } 139 | 140 | #[test] 141 | fn f64_not_nan() { 142 | let cases = vec![ 143 | [100, 0, 0, 0, 79, 103, 129, 0b1111_1110], 144 | [100, 0, 0, 0, 0, 0, 192, 0b0111_1101], 145 | [100, 0, 0, 0, 0, 0, 0, 0b0111_1101], 146 | [100, 0, 0, 0, 0, 0, 0, 0b1011_1111], 147 | [100, 0, 0, 0, 79, 103, 0b0111_1111, 0b1111_1111], 148 | ]; 149 | for case in cases { 150 | let buf = case.to_vec(); 151 | let res = deserialize_f64(&mut &buf[..]); 152 | assert!(!res.unwrap().is_nan()); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/deserializer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod borsh; 2 | mod floats; 3 | pub mod spl; 4 | 5 | use solana_idl::{Idl, IdlType}; 6 | use solana_sdk::pubkey::Pubkey; 7 | 8 | use crate::errors::ChainparserError; 9 | pub use crate::errors::ChainparserResult as Result; 10 | pub trait ChainparserDeserialize: Clone { 11 | fn u8(&self, buf: &mut &[u8]) -> Result; 12 | fn u16(&self, buf: &mut &[u8]) -> Result; 13 | fn u32(&self, buf: &mut &[u8]) -> Result; 14 | fn u64(&self, buf: &mut &[u8]) -> Result; 15 | fn u128(&self, buf: &mut &[u8]) -> Result; 16 | 17 | fn i8(&self, buf: &mut &[u8]) -> Result; 18 | fn i16(&self, buf: &mut &[u8]) -> Result; 19 | fn i32(&self, buf: &mut &[u8]) -> Result; 20 | fn i64(&self, buf: &mut &[u8]) -> Result; 21 | fn i128(&self, buf: &mut &[u8]) -> Result; 22 | 23 | fn f32(&self, buf: &mut &[u8]) -> Result; 24 | fn f64(&self, buf: &mut &[u8]) -> Result; 25 | 26 | fn bool(&self, buf: &mut &[u8]) -> Result; 27 | fn string(&self, buf: &mut &[u8]) -> Result; 28 | 29 | fn bytes(&self, buf: &mut &[u8]) -> Result>; 30 | fn pubkey(&self, buf: &mut &[u8]) -> Result; 31 | 32 | fn option(&self, buf: &mut &[u8]) -> Result; 33 | fn coption(&self, buf: &mut &[u8], inner: &IdlType) -> Result; 34 | } 35 | 36 | pub enum DeserializeProvider { 37 | Borsh(borsh::BorshDeserializer), 38 | Spl(spl::SplDeserializer), 39 | } 40 | 41 | impl TryFrom> for DeserializeProvider { 42 | type Error = ChainparserError; 43 | 44 | fn try_from(label: Option<&str>) -> std::result::Result { 45 | let label = label.unwrap_or("borsh"); 46 | match label { 47 | "borsh" => Ok(Self::Borsh(borsh::BorshDeserializer)), 48 | "spl" => Ok(Self::Spl(spl::SplDeserializer::new())), 49 | _ => Err(ChainparserError::UnsupportedDeserializer( 50 | label.to_string(), 51 | )), 52 | } 53 | } 54 | } 55 | 56 | impl TryFrom<&Idl> for DeserializeProvider { 57 | type Error = ChainparserError; 58 | 59 | fn try_from(idl: &Idl) -> std::result::Result { 60 | let label = idl.metadata.as_ref().and_then(|m| m.serializer.as_deref()); 61 | label.try_into() 62 | } 63 | } 64 | 65 | impl DeserializeProvider { 66 | pub fn borsh() -> Self { 67 | Self::Borsh(borsh::BorshDeserializer) 68 | } 69 | 70 | pub fn is_spl(&self) -> bool { 71 | matches!(self, DeserializeProvider::Spl(_)) 72 | } 73 | 74 | pub fn is_borsh(&self) -> bool { 75 | matches!(self, DeserializeProvider::Borsh(_)) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/deserializer/spl.rs: -------------------------------------------------------------------------------- 1 | use solana_idl::IdlType; 2 | use solana_sdk::pubkey::Pubkey; 3 | use TryFrom; 4 | 5 | use super::{borsh::BorshDeserializer, ChainparserDeserialize}; 6 | use crate::{ 7 | errors::{ChainparserError, ChainparserResult as Result}, 8 | idl, 9 | }; 10 | 11 | #[derive(Clone, Copy)] 12 | pub struct SplDeserializer { 13 | borsh: BorshDeserializer, 14 | } 15 | 16 | impl SplDeserializer { 17 | pub(crate) fn new() -> Self { 18 | Self { 19 | borsh: BorshDeserializer, 20 | } 21 | } 22 | } 23 | 24 | impl ChainparserDeserialize for SplDeserializer { 25 | fn u8(&self, buf: &mut &[u8]) -> Result { 26 | self.borsh.u8(buf) 27 | } 28 | 29 | fn u16(&self, buf: &mut &[u8]) -> Result { 30 | self.borsh.u16(buf) 31 | } 32 | 33 | fn u32(&self, buf: &mut &[u8]) -> Result { 34 | self.borsh.u32(buf) 35 | } 36 | 37 | fn u64(&self, buf: &mut &[u8]) -> Result { 38 | self.borsh.u64(buf) 39 | } 40 | 41 | fn u128(&self, buf: &mut &[u8]) -> Result { 42 | self.borsh.u128(buf) 43 | } 44 | 45 | fn i8(&self, buf: &mut &[u8]) -> Result { 46 | self.borsh.i8(buf) 47 | } 48 | 49 | fn i16(&self, buf: &mut &[u8]) -> Result { 50 | self.borsh.i16(buf) 51 | } 52 | 53 | fn i32(&self, buf: &mut &[u8]) -> Result { 54 | self.borsh.i32(buf) 55 | } 56 | 57 | fn i64(&self, buf: &mut &[u8]) -> Result { 58 | self.borsh.i64(buf) 59 | } 60 | 61 | fn i128(&self, buf: &mut &[u8]) -> Result { 62 | self.borsh.i128(buf) 63 | } 64 | 65 | fn f32(&self, buf: &mut &[u8]) -> Result { 66 | self.borsh.f32(buf) 67 | } 68 | 69 | fn f64(&self, buf: &mut &[u8]) -> Result { 70 | self.borsh.f64(buf) 71 | } 72 | 73 | fn bool(&self, buf: &mut &[u8]) -> Result { 74 | self.borsh.bool(buf) 75 | } 76 | 77 | fn string(&self, buf: &mut &[u8]) -> Result { 78 | self.borsh.string(buf) 79 | } 80 | 81 | fn bytes(&self, buf: &mut &[u8]) -> Result> { 82 | self.borsh.bytes(buf) 83 | } 84 | 85 | fn pubkey(&self, buf: &mut &[u8]) -> Result { 86 | let key = &buf[0..32]; 87 | let res = Pubkey::try_from(key).map_err(|e| { 88 | ChainparserError::TryFromSliceError( 89 | "pubkey".to_string(), 90 | e, 91 | buf.to_vec(), 92 | ) 93 | })?; 94 | *buf = &buf[32..]; 95 | Ok(res) 96 | } 97 | 98 | fn option(&self, _buf: &mut &[u8]) -> Result { 99 | Err(ChainparserError::DeserializerDoesNotSupportType( 100 | "spl".to_string(), 101 | "option".to_string(), 102 | )) 103 | } 104 | 105 | fn coption(&self, buf: &mut &[u8], inner: &IdlType) -> Result { 106 | if buf.len() < 4 { 107 | return Err(ChainparserError::InvalidDataToDeserialize( 108 | "coption".to_string(), 109 | "buf too short".to_string(), 110 | buf.to_vec(), 111 | )); 112 | } 113 | 114 | let tag = &buf[0..4]; 115 | *buf = &buf[4..]; 116 | match *tag { 117 | [0, 0, 0, 0] => { 118 | // COption is constant size, meaning None and Some take the same space. 119 | // In case of None it is filled with `0`s. Therefore in order to know 120 | // how far to consume the buffer we need to know the size of the inner 121 | // type without deserializing its data. 122 | 123 | // TODO(thlorenz): need the type_map here in order to pass it to idl_type_bytes to 124 | // resolve defined types, otherwise we can't deserialize COption with defined types 125 | // as inner 126 | if let Some(byte_len) = idl::idl_type_bytes(inner, None) { 127 | *buf = &buf[byte_len..]; 128 | Ok(false) 129 | } else { 130 | Err(ChainparserError::InvalidDataToDeserialize( 131 | "coption".to_string(), 132 | "byte size of inner type needs to be known when it is None" 133 | .to_string(), 134 | buf.to_vec(), 135 | )) 136 | } 137 | } 138 | [1, 0, 0, 0] => Ok(true), 139 | _ => Err(ChainparserError::InvalidDataToDeserialize( 140 | "coption".to_string(), 141 | "invalid tag".to_string(), 142 | tag.to_vec(), 143 | )), 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/discriminator/match_discriminator.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, ops::Deref}; 2 | 3 | use arrayref::array_ref; 4 | use solana_idl::{IdlType, IdlTypeDefinition, IdlTypeDefinitionTy}; 5 | 6 | use crate::idl; 7 | 8 | // ----------------- 9 | // Matcher 10 | // ----------------- 11 | #[derive(Debug)] 12 | pub enum Matcher { 13 | COption(usize, usize), 14 | Bool(usize), 15 | } 16 | 17 | impl TryFrom<(&IdlType, &HashMap, usize)> 18 | for Matcher 19 | { 20 | type Error = (); 21 | 22 | fn try_from( 23 | (ty, type_map, offset): ( 24 | &IdlType, 25 | &HashMap, 26 | usize, 27 | ), 28 | ) -> Result { 29 | match ty { 30 | IdlType::COption(inner) => { 31 | let inner_size = 32 | idl::idl_type_bytes(inner, Some(type_map)).unwrap_or(0); 33 | Ok(Matcher::COption(offset, inner_size)) 34 | } 35 | IdlType::Bool => Ok(Matcher::Bool(offset)), 36 | _ => Err(()), 37 | } 38 | } 39 | } 40 | 41 | impl Matcher { 42 | fn matches(&self, buf: &[u8]) -> bool { 43 | use Matcher::*; 44 | match self { 45 | COption(offset, _) => { 46 | let src = array_ref![buf, *offset, 4]; 47 | matches!(src, [1, 0, 0, 0]) || matches!(src, [0, 0, 0, 0]) 48 | } 49 | Bool(offset) => { 50 | let src = array_ref![buf, *offset, 1]; 51 | matches!(src, [0] | [1]) 52 | } 53 | } 54 | } 55 | } 56 | 57 | // ----------------- 58 | // MatchDiscriminators 59 | // ----------------- 60 | #[derive(Debug)] 61 | pub struct MatchDiscriminators(Vec); 62 | impl From<(&[IdlTypeDefinition], &HashMap)> 63 | for MatchDiscriminators 64 | { 65 | fn from( 66 | (accounts, type_map): ( 67 | &[IdlTypeDefinition], 68 | &HashMap, 69 | ), 70 | ) -> Self { 71 | let mut discs = accounts 72 | .iter() 73 | .flat_map(|acc| MatchDiscriminator::new(acc.clone(), type_map)) 74 | .collect::>(); 75 | discs.sort_by_key(|f| f.min_total_size); 76 | Self(discs) 77 | } 78 | } 79 | 80 | impl Deref for MatchDiscriminators { 81 | type Target = Vec; 82 | 83 | fn deref(&self) -> &Self::Target { 84 | &self.0 85 | } 86 | } 87 | 88 | impl MatchDiscriminators { 89 | pub fn find_match(&self, buf: &[u8]) -> Option { 90 | self.find_matching_disc(buf) 91 | .map(|disc| disc.account.clone()) 92 | } 93 | 94 | pub fn find_match_name(&self, buf: &[u8]) -> Option<&str> { 95 | self.find_matching_disc(buf).map(|disc| disc.account_name()) 96 | } 97 | 98 | fn find_matching_disc(&self, buf: &[u8]) -> Option<&MatchDiscriminator> { 99 | let mut candidates = Vec::new(); 100 | for disc in self.iter() { 101 | if disc.matches_account(buf) { 102 | // if sizes match exactly as well then this is the best match 103 | if disc.min_total_size == buf.len() { 104 | return Some(disc); 105 | } else { 106 | candidates.push(disc); 107 | } 108 | } 109 | } 110 | // Did not find exact size match, thus we pick the discriminator 111 | // that had to match most fields 112 | let mut best_candidate = None::<&MatchDiscriminator>; 113 | for candidate in candidates { 114 | if let Some(disc) = best_candidate { 115 | if candidate.matchers.len() > disc.matchers.len() { 116 | best_candidate = Some(candidate); 117 | } 118 | } else { 119 | best_candidate = Some(candidate); 120 | } 121 | } 122 | best_candidate 123 | } 124 | } 125 | 126 | // ----------------- 127 | // MatchDiscriminator 128 | // ----------------- 129 | #[derive(Debug)] 130 | pub struct MatchDiscriminator { 131 | pub account: IdlTypeDefinition, 132 | min_total_size: usize, 133 | matchers: Vec, 134 | } 135 | 136 | impl MatchDiscriminator { 137 | pub fn new( 138 | account: IdlTypeDefinition, 139 | type_map: &HashMap, 140 | ) -> Option { 141 | let account_sizes = base_account_sizes(&account, type_map); 142 | match account_sizes { 143 | Some((field_sizes, field_offsets)) => { 144 | let min_total_size = field_sizes.iter().sum(); 145 | let matchers = 146 | account_matchers(&account, type_map, &field_offsets); 147 | // TODO(thlorenz): should require at least have one multi byte matcher 148 | if matchers.is_empty() { 149 | None 150 | } else { 151 | Some(Self { 152 | account, 153 | min_total_size, 154 | matchers, 155 | }) 156 | } 157 | } 158 | _ => None, 159 | } 160 | } 161 | 162 | pub fn account_name(&self) -> &str { 163 | &self.account.name 164 | } 165 | 166 | fn matches_account(&self, buf: &[u8]) -> bool { 167 | if buf.len() < self.min_total_size { 168 | return false; 169 | } 170 | self.matchers.iter().all(|matcher| matcher.matches(buf)) 171 | } 172 | } 173 | 174 | fn account_matchers( 175 | account: &IdlTypeDefinition, 176 | type_map: &HashMap, 177 | offsets: &[usize], 178 | ) -> Vec { 179 | match &account.ty { 180 | IdlTypeDefinitionTy::Struct { fields } => { 181 | let mut matchers = Vec::new(); 182 | for (field, offset) in fields.iter().zip(offsets) { 183 | if let Ok(matcher) = 184 | Matcher::try_from((&field.ty, type_map, *offset)) 185 | { 186 | matchers.push(matcher) 187 | } 188 | } 189 | matchers 190 | } 191 | _ => Vec::new(), 192 | } 193 | } 194 | 195 | fn base_account_sizes( 196 | account: &IdlTypeDefinition, 197 | type_map: &HashMap, 198 | ) -> Option<(Vec, Vec)> { 199 | let mut offsets = Vec::new(); 200 | let mut sizes = Vec::new(); 201 | 202 | let mut offset = 0; 203 | 204 | match &account.ty { 205 | IdlTypeDefinitionTy::Struct { fields } => { 206 | for field in fields { 207 | if let Some(size) = 208 | idl::idl_type_bytes(&field.ty, Some(type_map)) 209 | { 210 | offsets.push(offset); 211 | sizes.push(size); 212 | offset += size; 213 | } 214 | } 215 | Some((sizes, offsets)) 216 | } 217 | _ => None, // accounts should always be structs 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/discriminator/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod match_discriminator; 2 | 3 | use solana_sdk::hash::hash; 4 | 5 | pub type DiscriminatorBytes = [u8; 8]; 6 | 7 | /// Derives the account discriminator form the account name using the same algorithm that anchor 8 | /// uses. 9 | pub fn account_discriminator(name: &str) -> DiscriminatorBytes { 10 | let mut discriminator = [0u8; 8]; 11 | let hashed = hash(format!("account:{name}").as_bytes()).to_bytes(); 12 | discriminator.copy_from_slice(&hashed[..8]); 13 | discriminator 14 | } 15 | 16 | pub fn discriminator_from_data(data: &[u8]) -> DiscriminatorBytes { 17 | let mut discriminator = [0u8; 8]; 18 | discriminator.copy_from_slice(&data[..8]); 19 | discriminator 20 | } 21 | 22 | #[cfg(test)] 23 | mod test { 24 | use super::*; 25 | 26 | #[test] 27 | fn account_discriminator_test() { 28 | let name = "VaultInfo"; 29 | let discriminator = account_discriminator(name); 30 | assert_eq!(discriminator, [133, 250, 161, 78, 246, 27, 55, 187]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | pub type ChainparserResult = Result; 4 | 5 | #[derive(Error, Debug)] 6 | pub enum ChainparserError { 7 | #[error("Format Error")] 8 | FormatError(#[from] std::fmt::Error), 9 | 10 | #[error("Borsh IO Error")] 11 | BorshIoError(#[from] borsh::maybestd::io::Error), 12 | 13 | #[error("Solana Idl Error")] 14 | SolanaIdlError(#[from] solana_idl::errors::IdlError), 15 | 16 | #[error("Deserializer '{0}' is not supported by chainsaw")] 17 | UnsupportedDeserializer(String), 18 | 19 | #[error("Borsh failed to deserialize type '{1}' ({0}")] 20 | BorshDeserializeTypeError(String, borsh::maybestd::io::Error, Vec), 21 | 22 | #[error("Borsh failed to deserialize float '{1}' ({0} {2:?}")] 23 | BorshDeserializeFloatError(String, borsh::maybestd::io::Error, Vec), 24 | 25 | #[error("Chainparser failed to deserialize type '{0}' ({1} {2:?}")] 26 | TryFromSliceError(String, std::array::TryFromSliceError, Vec), 27 | 28 | #[error("Borsh failed to deserialize type '{0}' ({1})")] 29 | CompositeDeserializeError(String, Box), 30 | 31 | #[error("Borsh failed to deserialize type for field '{0}' ({1})")] 32 | FieldDeserializeError(String, Box), 33 | 34 | #[error("Borsh failed to deserialize type for enum variant '{0}' ({1})")] 35 | EnumVariantDeserializeError(String, Box), 36 | 37 | #[error("Borsh failed to deserialize type for struct '{0}' ({1})")] 38 | StructDeserializeError(String, Box), 39 | 40 | #[error("Borsh failed to deserialize type for enum '{0}' ({1})")] 41 | EnumDeserializeError(String, Box), 42 | 43 | #[error("The '{0}' deserializer does not support type '{1}'")] 44 | DeserializerDoesNotSupportType(String, String), 45 | 46 | #[error( 47 | "Encountered '{1}' when trying to deserizalize type '{0}' from {2:?}" 48 | )] 49 | InvalidDataToDeserialize(String, String, Vec), 50 | 51 | #[error("Account {0} is requested to be deserialized but was not defined in the IDL")] 52 | UnknownAccount(String), 53 | 54 | #[error("Account with discriminator {0} is requested to be deserialized but was not defined in the IDL")] 55 | UnknownDiscriminatedAccount(String), 56 | 57 | #[error( 58 | "Could not find an account that matches the provided account data." 59 | )] 60 | CannotFindDeserializerForAccount, 61 | 62 | #[error("Account is requested to be deserialized Idl {0} version {1} has no accounts")] 63 | IdlHasNoAccountsAndCannotDeserializeAccountData(String, String), 64 | 65 | #[error("Account is requested to be via discriminator bytes but Idl {0} version {1} has no such accounts")] 66 | IdlHasNoAccountsDiscriminatedByDiscriminatorBytes(String, String), 67 | 68 | #[error("Type {0} is referenced but was not defined in the IDL")] 69 | CannotFindDefinedType(String), 70 | 71 | #[error("Variant with discriminant {0} does not exist")] 72 | InvalidEnumVariantDiscriminator(u8), 73 | 74 | #[error("Unable to parse JSON")] 75 | ParseJsonError(#[from] serde_json::Error), 76 | 77 | #[cfg(feature = "bson")] 78 | #[error("Raw Bson Error")] 79 | RawBsonError(#[from] bson::raw::Error), 80 | 81 | #[error("No IDL was added for the program {0}.")] 82 | CannotFindAccountDeserializerForProgramId(String), 83 | 84 | #[error("Unable to derive pubkey for the IDL to fetch")] 85 | IdlPubkeyError(#[from] solana_sdk::pubkey::PubkeyError), 86 | 87 | #[error("Unable to inflate IDl data ({0})")] 88 | IdlContainerShouldContainZlibData(String), 89 | 90 | #[error( 91 | "Failed to parse pubkey of the program to add IDL for '{0}' ({1})" 92 | )] 93 | FailedToParseIdlProgramPubkey(String, String), 94 | 95 | #[error( 96 | "Cannot parse account data with {0} bytes since the discriminator is at least {1} bytes" 97 | )] 98 | AccountDataTooShortForDiscriminatorBytes(usize, usize), 99 | } 100 | -------------------------------------------------------------------------------- /src/idl/encoder/idl_decoder.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use flate2::read::ZlibDecoder; 4 | use solana_idl::Idl; 5 | 6 | use super::IDL_HEADER_SIZE; 7 | use crate::errors::{ChainparserError, ChainparserResult}; 8 | 9 | /* Related anchor code: 10 | ```ts 11 | // Chop off account discriminator. 12 | let idlAccount = decodeIdlAccount(accountInfo.data.slice(8)); 13 | const inflatedIdl = inflate(idlAccount.data); 14 | return JSON.parse(utf8.decode(inflatedIdl)); 15 | 16 | const IDL_ACCOUNT_LAYOUT: borsh.Layout = borsh.struct([ 17 | borsh.publicKey("authority"), 18 | borsh.vecU8("data"), 19 | ]); 20 | 21 | export function decodeIdlAccount(data: Buffer): IdlProgramAccount { 22 | return IDL_ACCOUNT_LAYOUT.decode(data); 23 | } 24 | 25 | export function vecU8(property?: string): Layout { 26 | const length = u32("length"); 27 | const layout: Layout<{ data: Buffer }> = struct([ 28 | length, 29 | blob(offset(length, -length.span), "data"), 30 | ]); 31 | return new WrappedLayout( 32 | layout, 33 | ({ data }) => data, 34 | (data) => ({ data }), 35 | property 36 | ); 37 | } 38 | ``` 39 | **/ 40 | 41 | /// Parses the provided JSON string into an [Idl] struct. 42 | /// It attempts to parse it directly as a classic IDL and if that fails it 43 | /// will parse as the new anchor IDL format and then convert to the 44 | /// classic. 45 | pub fn try_parse_idl_json(json: &str) -> ChainparserResult { 46 | Ok(solana_idl::try_extract_classic_idl(json)?) 47 | } 48 | 49 | /// Same as [decode_idl_data] except that it strips the prefix bytes before 50 | /// unzipping the packed JSON. 51 | pub fn decode_idl_account_data( 52 | account_data: &[u8], 53 | ) -> ChainparserResult<(Idl, String)> { 54 | decode_idl_data(&account_data[IDL_HEADER_SIZE..]) 55 | } 56 | 57 | /// Unzips account data obtained from chain by first stripping the prefix 58 | /// bytes which aren't the zip data and then unpacking the containted string. 59 | pub fn unzip_idl_account_json(bytes: &[u8]) -> ChainparserResult { 60 | unzip_bytes(&bytes[IDL_HEADER_SIZE..]) 61 | } 62 | 63 | /// Decodes IDL data by first unzipping the provided data and then parsing 64 | /// the contained JSON. 65 | fn decode_idl_data(data: &[u8]) -> ChainparserResult<(Idl, String)> { 66 | let json = unzip_bytes(data)?; 67 | let idl: Idl = solana_idl::try_extract_classic_idl(&json)?; 68 | Ok((idl, json)) 69 | } 70 | 71 | /// Unzips the provided [bytes] into a string. 72 | fn unzip_bytes(bytes: &[u8]) -> ChainparserResult { 73 | let mut zlib = ZlibDecoder::new(bytes); 74 | let mut write = String::new(); 75 | zlib.read_to_string(&mut write).map_err(|err| { 76 | ChainparserError::IdlContainerShouldContainZlibData(err.to_string()) 77 | })?; 78 | Ok(write) 79 | } 80 | -------------------------------------------------------------------------------- /src/idl/encoder/idl_encoder.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use flate2::write::ZlibEncoder; 4 | use solana_idl::Idl; 5 | use solana_sdk::pubkey::Pubkey; 6 | 7 | use crate::errors::ChainparserResult; 8 | 9 | /* 10 | * Structure of an Anchor IDL account: 11 | 12 | ```rust 13 | #[derive(Debug)] 14 | pub struct IdlAccount { 15 | // Address that can modify the IDL. 16 | pub authority: Pubkey, 17 | // Length of compressed idl bytes. 18 | pub data_len: u32, 19 | // Followed by compressed idl bytes. 20 | } 21 | ``` 22 | 23 | Discriminator 8 bytes (always the same) 24 | 25 | /* 0000: */ 0x18, 0x46, 0x62, 0xbf, 26 | /* 0004: */ 0x3a, 0x90, 0x7b, 0x9e, 27 | 28 | Struct start ... 29 | Pubkey of IDL program (Address that can modify the IDL) (32 bytes), example: 30 | 31 | /* 0008: */ 0x06, 0x9b, 0xe9, 0xd3, 32 | /* 0012: */ 0x33, 0x01, 0x1c, 0x15, 33 | /* 0016: */ 0x29, 0x75, 0x23, 0x79, 34 | /* 0020: */ 0xd8, 0xf5, 0x04, 0x6d, 35 | /* 0024: */ 0xcd, 0x15, 0x7d, 0xe3, 36 | /* 0028: */ 0x10, 0xbe, 0x9e, 0x04, 37 | /* 0032: */ 0xb0, 0x31, 0x31, 0xcb, 38 | 39 | Data len of data (before compression, i.e. Vec::len()) (4 bytes), example: 40 | 41 | /* 0036: */ 0xea, 0x71, 0xd9, 0x7e, 42 | /* 0040: */ 0x6c, 0x01, 0x00, 0x00, 43 | 44 | ZLib header: always 0x78 0x9c 45 | 46 | /* 0044: */ 0x78, 0x9c, 47 | ...data 48 | */ 49 | 50 | #[rustfmt::skip] 51 | const DISCRIMINATOR: [u8; 8] = [ 52 | 0x18, 0x46, 0x62, 0xbf, 53 | 0x3a, 0x90, 0x7b, 0x9e, 54 | ]; 55 | 56 | pub fn encode_idl_account( 57 | program_id: &Pubkey, 58 | idl: &Idl, 59 | ) -> ChainparserResult> { 60 | let json = serde_json::to_vec(idl)?; 61 | 62 | let pubkey_vec = program_id.to_bytes().to_vec(); 63 | let data_len_bytes = (json.len() as u32).to_le_bytes().to_vec(); 64 | let zipped = zip_bytes(&json)?; 65 | 66 | let full_vec = 67 | [DISCRIMINATOR.to_vec(), pubkey_vec, data_len_bytes, zipped].concat(); 68 | Ok(full_vec) 69 | } 70 | 71 | pub fn encode_idl(idl: &Idl) -> ChainparserResult> { 72 | let json = serde_json::to_vec(idl)?; 73 | zip_bytes(&json) 74 | } 75 | 76 | pub fn encode_idl_account_json( 77 | program_id: &Pubkey, 78 | idl_json: &str, 79 | ) -> ChainparserResult> { 80 | let json_bytes = idl_json.as_bytes(); 81 | let pubkey_vec = program_id.to_bytes().to_vec(); 82 | let data_len_bytes = (json_bytes.len() as u32).to_le_bytes().to_vec(); 83 | let zipped = zip_bytes(idl_json.as_bytes())?; 84 | 85 | let full_vec = 86 | [DISCRIMINATOR.to_vec(), pubkey_vec, data_len_bytes, zipped].concat(); 87 | Ok(full_vec) 88 | } 89 | 90 | fn zip_bytes(bytes: &[u8]) -> ChainparserResult> { 91 | let mut encoder = 92 | ZlibEncoder::new(Vec::new(), flate2::Compression::default()); 93 | encoder.write_all(bytes)?; 94 | Ok(encoder.finish()?) 95 | } 96 | -------------------------------------------------------------------------------- /src/idl/encoder/mod.rs: -------------------------------------------------------------------------------- 1 | mod idl_decoder; 2 | mod idl_encoder; 3 | 4 | pub use idl_decoder::*; 5 | pub use idl_encoder::*; 6 | 7 | // anchor:cli/src/lib.rs 8 | pub const IDL_HEADER_SIZE: usize = 44; 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | use base64::{engine::general_purpose, Engine as _}; 13 | use solana_idl::Idl; 14 | use solana_sdk::pubkey::Pubkey; 15 | 16 | use super::*; 17 | 18 | pub fn base64_decode(data: &str) -> Vec { 19 | general_purpose::STANDARD.decode(data).unwrap() 20 | } 21 | 22 | // "{\"version\":\"4.15.7\",\"name\":\"minimal\",\"instructions\":[]}"; 23 | const BASE64_ENCODED_ZIP_OF_IDL_JSON: &str = 24 | "GEZivzqQe57cMppXObv8MtuJT6/acEkBfTCpZ95AxHl8kvhuCiZU4j0AAAB4nAEyAM3/eyJ2ZXJzaW9uIjoiMC4xLjAiLCJuYW1lIjoiZm9vIiwiaW5zdHJ1Y3Rpb25zIjpbXX2IfRAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="; 25 | 26 | fn get_basic_idl() -> Vec { 27 | base64_decode(BASE64_ENCODED_ZIP_OF_IDL_JSON.trim_end()) 28 | } 29 | 30 | #[test] 31 | fn unzip_json_from_basic_idl_account_and_reencode() { 32 | let idl_account_data = get_basic_idl(); 33 | let json = unzip_idl_account_json(&idl_account_data) 34 | .expect("should unzip to JSON"); 35 | assert_eq!( 36 | json, 37 | "{\"version\":\"0.1.0\",\"name\":\"foo\",\"instructions\":[]}" 38 | ); 39 | let some_pubkey = Pubkey::new_unique(); 40 | let encoded = encode_idl_account_json(&some_pubkey, &json).unwrap(); 41 | assert_eq!( 42 | encoded[IDL_HEADER_SIZE..], 43 | idl_account_data[IDL_HEADER_SIZE..encoded.len()] 44 | ); 45 | } 46 | 47 | #[test] 48 | fn roundtrip_minimal_idl() { 49 | const BASIC_IDL_JSON: &str = 50 | "{\"version\":\"0.1.0\",\"name\":\"foo\",\"instructions\":[]}"; 51 | 52 | let some_pubkey = Pubkey::new_unique(); 53 | let idl: Idl = serde_json::from_str(BASIC_IDL_JSON).unwrap(); 54 | let encoded = encode_idl_account(&some_pubkey, &idl).unwrap(); 55 | let (decoded_idl, decoded_json) = 56 | decode_idl_account_data(&encoded).unwrap(); 57 | 58 | assert_eq!(decoded_idl, idl); 59 | assert_eq!(decoded_json, BASIC_IDL_JSON); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/idl/idl_address.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use solana_idl::{IdlType, IdlTypeDefinitionTy}; 4 | use solana_sdk::pubkey::Pubkey; 5 | 6 | use super::IdlProvider; 7 | use crate::errors::ChainparserResult; 8 | 9 | const ANCHOR_SEED: &str = "anchor:idl"; 10 | const SHANK_SEED: &str = "shank:idl"; 11 | 12 | /// Resolves the address of the account where the program [IDL] is stored. 13 | /// 14 | /// - [provider] that uploaded the [IDL] 15 | /// - [program_id] address of the program 16 | pub fn try_idl_address( 17 | provider: &IdlProvider, 18 | program_id: &Pubkey, 19 | ) -> ChainparserResult { 20 | let (base, _) = Pubkey::find_program_address(&[], program_id); 21 | let seed = match provider { 22 | IdlProvider::Anchor => ANCHOR_SEED, 23 | IdlProvider::Shank => SHANK_SEED, 24 | }; 25 | let key = Pubkey::create_with_seed(&base, seed, program_id)?; 26 | Ok(key) 27 | } 28 | 29 | /// Resolves the addresses of IDL accounts for `(anchor, shank)`. 30 | pub fn get_idl_addresses( 31 | program_id: &Pubkey, 32 | ) -> (Option, Option) { 33 | let (base, _) = Pubkey::find_program_address(&[], program_id); 34 | let anchor = Pubkey::create_with_seed(&base, ANCHOR_SEED, program_id).ok(); 35 | let shank = Pubkey::create_with_seed(&base, SHANK_SEED, program_id).ok(); 36 | (anchor, shank) 37 | } 38 | 39 | pub fn is_idl_addess(program_id: &Pubkey, address: &Pubkey) -> bool { 40 | let (anchor, shank) = get_idl_addresses(program_id); 41 | let is_anchor_idl = matches!(anchor, Some(anchor) if anchor == *address); 42 | if is_anchor_idl { 43 | return true; 44 | } 45 | matches!(shank, Some(shank) if shank == *address) 46 | } 47 | 48 | pub(crate) fn idl_type_bytes( 49 | ty: &IdlType, 50 | type_map: Option<&HashMap>, 51 | ) -> Option { 52 | use IdlType::*; 53 | match ty { 54 | U8 => Some(1), 55 | U16 => Some(2), 56 | U32 => Some(4), 57 | U64 => Some(8), 58 | U128 => Some(16), 59 | I8 => Some(1), 60 | I16 => Some(2), 61 | I32 => Some(4), 62 | I64 => Some(8), 63 | I128 => Some(16), 64 | F32 => Some(4), 65 | F64 => Some(8), 66 | Bool => Some(1), 67 | PublicKey => Some(32), 68 | IdlType::Array(inner, len) => { 69 | idl_type_bytes(inner, type_map).map(|x| x * len) 70 | } 71 | IdlType::COption(inner) => { 72 | idl_type_bytes(inner, type_map).map(|x| x + 4) 73 | } 74 | Defined(s) => { 75 | if let Some(ty) = type_map.and_then(|map| map.get(s)) { 76 | idl_def_bytes(ty, type_map) 77 | } else { 78 | None 79 | } 80 | } 81 | // NOTE: for Option the size is different depending if it is None or Some 82 | _ => None, 83 | } 84 | } 85 | pub(crate) fn idl_def_bytes( 86 | ty: &IdlTypeDefinitionTy, 87 | type_map: Option<&HashMap>, 88 | ) -> Option { 89 | match ty { 90 | IdlTypeDefinitionTy::Struct { fields } => { 91 | let mut struct_size = 0; 92 | for field in fields { 93 | if let Some(size) = idl_type_bytes(&field.ty, type_map) { 94 | struct_size += size; 95 | } else { 96 | return None; 97 | } 98 | } 99 | Some(struct_size) 100 | } 101 | IdlTypeDefinitionTy::Enum { variants } => { 102 | if variants.iter().all(|variant| variant.fields.is_none()) { 103 | return Some(1); 104 | } 105 | // if variants have different sizes then we cannot determine the size 106 | // it will take without data 107 | None 108 | } 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod test { 114 | use std::str::FromStr; 115 | 116 | use super::*; 117 | 118 | pub fn str_to_pubkey(pubkey_str: &str) -> Pubkey { 119 | FromStr::from_str(pubkey_str).expect("pubkey from string") 120 | } 121 | 122 | #[test] 123 | fn idl_address_test() { 124 | let program_id = 125 | str_to_pubkey("cndy3Z4yapfJBmL3ShUp5exZKqR3z33thTzeNMm2gRZ"); 126 | 127 | let anchor_idl_address = 128 | try_idl_address(&IdlProvider::Anchor, &program_id).unwrap(); 129 | let shank_idl_address = 130 | try_idl_address(&IdlProvider::Shank, &program_id).unwrap(); 131 | 132 | assert_eq!( 133 | anchor_idl_address.to_string(), 134 | "CggtNXgCye2qk7fLohonNftqaKT35GkuZJwHrRghEvSF" 135 | ); 136 | assert_eq!( 137 | shank_idl_address.to_string(), 138 | "AEUhdmwzSea7oYDWhAiSBArqq6tBLFNNZZ448wfbaV3Z" 139 | ); 140 | } 141 | 142 | #[test] 143 | fn get_idl_addresses_test() { 144 | let (anchor, shank) = get_idl_addresses(&str_to_pubkey( 145 | "cndy3Z4yapfJBmL3ShUp5exZKqR3z33thTzeNMm2gRZ", 146 | )); 147 | assert_eq!( 148 | anchor.unwrap().to_string(), 149 | "CggtNXgCye2qk7fLohonNftqaKT35GkuZJwHrRghEvSF" 150 | ); 151 | assert_eq!( 152 | shank.unwrap().to_string(), 153 | "AEUhdmwzSea7oYDWhAiSBArqq6tBLFNNZZ448wfbaV3Z" 154 | ); 155 | } 156 | 157 | #[test] 158 | fn is_idl_address_test() { 159 | let program_id = 160 | str_to_pubkey("cndy3Z4yapfJBmL3ShUp5exZKqR3z33thTzeNMm2gRZ"); 161 | assert!(is_idl_addess( 162 | &program_id, 163 | &str_to_pubkey("CggtNXgCye2qk7fLohonNftqaKT35GkuZJwHrRghEvSF") 164 | )); 165 | assert!(is_idl_addess( 166 | &program_id, 167 | &str_to_pubkey("AEUhdmwzSea7oYDWhAiSBArqq6tBLFNNZZ448wfbaV3Z") 168 | )); 169 | assert!(!is_idl_addess(&program_id, &Pubkey::default())); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/idl/idl_provider.rs: -------------------------------------------------------------------------------- 1 | //! Loading IDLs from Data or JSON and convert into a format storable in the Validator 2 | use std::{fs, str::FromStr}; 3 | 4 | use solana_sdk::{ 5 | account::{Account, AccountSharedData}, 6 | pubkey::Pubkey, 7 | stake_history::Epoch, 8 | }; 9 | 10 | use super::{encode_idl_account_json, try_idl_address, IdlProvider}; 11 | use crate::errors::{ChainparserError, ChainparserResult}; 12 | 13 | /// Given the full path to an IDL JSON file, returns the [Pubkey] of the IDL 14 | /// account and an [AccountSharedData] that can be loaded into the validator at 15 | /// that address. 16 | /// Prepares leading data following IDL account specs (anchor). 17 | /// For more se [account_shared_data_for_idl_json_file]. 18 | pub fn idl_pubkey_and_deployable_data( 19 | program_id: &str, 20 | idl_path: &str, 21 | ) -> ChainparserResult<(Pubkey, AccountSharedData)> { 22 | let program_pubkey = Pubkey::from_str(program_id).map_err(|err| { 23 | ChainparserError::FailedToParseIdlProgramPubkey( 24 | program_id.to_string(), 25 | format!("{:#?}", err), 26 | ) 27 | })?; 28 | let idl_pubkey = try_idl_address(&IdlProvider::Anchor, &program_pubkey)?; 29 | 30 | Ok(( 31 | idl_pubkey, 32 | account_shared_data_for_idl_json_file(program_pubkey, idl_path)?, 33 | )) 34 | } 35 | 36 | /// Given the full path to an IDL JSON file, returns an [AccountSharedData] that 37 | /// can be loaded into the validator. 38 | /// Note that the data includes the program_pubkey [Pubkey] as the owner of the 39 | /// IDL account in the data that is stored in the IDL account. 40 | /// This is necessary for tools like the explorer to accept this as a proper 41 | /// IDL account. 42 | pub fn account_shared_data_for_idl_json_file( 43 | program_pubkey: Pubkey, 44 | idl_path: &str, 45 | ) -> ChainparserResult { 46 | let idl_json = fs::read_to_string(idl_path)?; 47 | account_shared_data_for_idl_json(program_pubkey, &idl_json) 48 | } 49 | 50 | /// Given the [Pubkey] of a program and the JSON of an IDL, returns an 51 | /// [AccountSharedData] that can be loaded into the validator. 52 | pub fn account_shared_data_for_idl_json( 53 | program_pubkey: Pubkey, 54 | idl_json: &str, 55 | ) -> ChainparserResult { 56 | // 1. Encode into zip 57 | let encoded_idl_data = encode_idl_account_json(&program_pubkey, idl_json)?; 58 | 59 | // 2. obtain account shared data from it 60 | let account_shared_data = 61 | account_shared_data_for_idl(program_pubkey, encoded_idl_data)?; 62 | Ok(account_shared_data) 63 | } 64 | 65 | fn account_shared_data_for_idl( 66 | program_pubkey: Pubkey, 67 | encoded_idl_data: Vec, 68 | ) -> ChainparserResult { 69 | let lamports = u16::MAX as u64; 70 | let data = encoded_idl_data; 71 | let executable = false; 72 | let rent_epoch = Epoch::default(); 73 | let account = Account { 74 | lamports, 75 | data, 76 | owner: program_pubkey, 77 | executable, 78 | rent_epoch, 79 | }; 80 | Ok(account.into()) 81 | } 82 | -------------------------------------------------------------------------------- /src/idl/idl_retriever.rs: -------------------------------------------------------------------------------- 1 | use log::trace; 2 | use solana_idl::Idl; 3 | use solana_sdk::pubkey::Pubkey; 4 | 5 | use super::{decode_idl_account_data, try_idl_address, IdlProvider}; 6 | use crate::{errors::ChainparserResult, traits::AccountProvider}; 7 | 8 | pub fn try_find_idl_for_program( 9 | account_provider: &T, 10 | program_id: &Pubkey, 11 | idl_provider: &IdlProvider, 12 | ) -> ChainparserResult> { 13 | let idl_address = try_idl_address(idl_provider, program_id)?; 14 | match account_provider.get_account(&idl_address) { 15 | Some((account, _)) => { 16 | let (idl, json) = decode_idl_account_data(&account.data)?; 17 | if std::option_env!("TRACE_RETRIEVED_IDL").is_some() { 18 | trace!("{}", json); 19 | } 20 | Ok(Some(idl)) 21 | } 22 | None => Ok(None), 23 | } 24 | } 25 | 26 | pub fn try_find_idl_and_provider_for_program( 27 | account_provider: &T, 28 | program_id: &Pubkey, 29 | ) -> ChainparserResult> { 30 | for idl_provider in super::IDL_PROVIDERS { 31 | if let Some(idl) = try_find_idl_for_program( 32 | account_provider, 33 | program_id, 34 | idl_provider, 35 | )? { 36 | return Ok(Some((idl, idl_provider.clone()))); 37 | } 38 | } 39 | Ok(None) 40 | } 41 | -------------------------------------------------------------------------------- /src/idl/mod.rs: -------------------------------------------------------------------------------- 1 | mod encoder; 2 | mod idl_address; 3 | mod idl_provider; 4 | mod idl_retriever; 5 | 6 | use std::fmt; 7 | 8 | pub use encoder::*; 9 | pub use idl_address::*; 10 | pub use idl_provider::*; 11 | pub use idl_retriever::*; 12 | 13 | /// The provider responsible for generating the IDL. 14 | /// Some providers like [Anchor] also prefix the account data in a specific way, i.e. by adding a 15 | /// discriminator 16 | #[derive(Clone, PartialEq, Eq, Hash)] 17 | pub enum IdlProvider { 18 | Anchor, 19 | Shank, 20 | } 21 | 22 | pub const IDL_PROVIDERS: &[IdlProvider; 2] = 23 | &[IdlProvider::Anchor, IdlProvider::Shank]; 24 | 25 | impl TryFrom<&str> for IdlProvider { 26 | type Error = (); 27 | fn try_from(s: &str) -> Result { 28 | match s { 29 | "anchor" => Ok(Self::Anchor), 30 | "shank" => Ok(Self::Shank), 31 | _ => Err(()), 32 | } 33 | } 34 | } 35 | 36 | impl fmt::Display for IdlProvider { 37 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 38 | match self { 39 | Self::Anchor => write!(f, "anchor"), 40 | Self::Shank => write!(f, "shank"), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ixs/discriminator.rs: -------------------------------------------------------------------------------- 1 | use heck::ToSnakeCase; 2 | use solana_idl::IdlInstruction; 3 | use solana_sdk::hash; 4 | 5 | // Namespace for calculating instruction sighash signatures for any instruction 6 | // not affecting program state. 7 | const SIGHASH_GLOBAL_NAMESPACE: &str = "global"; 8 | 9 | pub fn discriminator_from_ix(ix: &IdlInstruction) -> Vec { 10 | ix.discriminant 11 | .as_ref() 12 | // Newer Anchor Versions >=0.30 add the discriminator value which 13 | // is moved to the `bytes` property 14 | // Shank adds the indes of the instruction to the `value` property 15 | // instead. 16 | .map(|x| x.bytes.clone().unwrap_or(vec![x.value])) 17 | // If we don't find it in either we assume it is an older anchor IDL 18 | // and derive the discriminator the same way that anchor did before. 19 | .unwrap_or_else(|| { 20 | anchor_sighash(SIGHASH_GLOBAL_NAMESPACE, &ix.name).to_vec() 21 | }) 22 | } 23 | 24 | /// Replicates the mechanism that anchor used in order to derive a discriminator 25 | /// from the name of an instruction. 26 | fn anchor_sighash(namespace: &str, ix_name: &str) -> [u8; 8] { 27 | // NOTE: even though the name of the ix is lower camel cased in the IDL it 28 | // seems that the IX discriminator is derived from the snake case version 29 | // (see discriminator_for_house_initialize test below which came from a real case) 30 | let ix_name = ix_name.to_snake_case(); 31 | 32 | let preimage = format!("{namespace}:{ix_name}"); 33 | 34 | let mut sighash = [0u8; 8]; 35 | sighash.copy_from_slice(&hash::hash(preimage.as_bytes()).to_bytes()[..8]); 36 | sighash 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::*; 42 | 43 | #[test] 44 | fn discriminator_for_delegate() { 45 | let sighash = anchor_sighash(SIGHASH_GLOBAL_NAMESPACE, "delegate"); 46 | assert_eq!(sighash, [90, 147, 75, 178, 85, 88, 4, 137]); 47 | } 48 | 49 | #[test] 50 | fn discriminator_for_increment() { 51 | let sighash = anchor_sighash(SIGHASH_GLOBAL_NAMESPACE, "increment"); 52 | assert_eq!(sighash, [11, 18, 104, 9, 104, 174, 59, 33]); 53 | } 54 | 55 | #[test] 56 | fn discriminator_for_add_entity() { 57 | let sighash = anchor_sighash(SIGHASH_GLOBAL_NAMESPACE, "add_entity"); 58 | assert_eq!(sighash, [163, 241, 57, 35, 244, 244, 48, 57]); 59 | } 60 | 61 | #[test] 62 | fn discriminator_for_process_undelegation() { 63 | let sighash = 64 | anchor_sighash(SIGHASH_GLOBAL_NAMESPACE, "process_undelegation"); 65 | assert_eq!(sighash, [196, 28, 41, 206, 48, 37, 51, 167]); 66 | } 67 | #[test] 68 | fn discriminator_for_house_initialize() { 69 | // 8d 53 7d 73 a2 98 51 e7 e1 5f 47 02 00 00 00 00 70 | let sighash = 71 | anchor_sighash(SIGHASH_GLOBAL_NAMESPACE, "house_initialize"); 72 | assert_eq!(sighash, [141, 83, 125, 115, 162, 152, 81, 231]); 73 | let sighash = 74 | anchor_sighash(SIGHASH_GLOBAL_NAMESPACE, "houseInitialize"); 75 | assert_eq!(sighash, [141, 83, 125, 115, 162, 152, 81, 231]); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/ixs/instruction_mapper.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use log::*; 3 | use std::{collections::HashMap, str::FromStr}; 4 | 5 | use solana_idl::{Idl, IdlInstruction}; 6 | use solana_sdk::pubkey::Pubkey; 7 | 8 | use super::{discriminator::discriminator_from_ix, ParseableInstruction}; 9 | 10 | #[rustfmt::skip] 11 | lazy_static! { 12 | pub static ref BUILTIN_PROGRAMS: HashMap = [ 13 | ("System Program" , "11111111111111111111111111111111") , 14 | ("BPF Upgradeable Loader" , "BPFLoaderUpgradeab1e11111111111111111111111"), 15 | ("BPF Loader 2" , "BPFLoader2111111111111111111111111111111111"), 16 | ("Config Program" , "Config1111111111111111111111111111111111111"), 17 | ("Feature Program" , "Feature111111111111111111111111111111111111"), 18 | ("Native Loader" , "NativeLoader1111111111111111111111111111111"), 19 | ("Stake Program" , "Stake11111111111111111111111111111111111111"), 20 | ("Sysvar" , "Sysvar1111111111111111111111111111111111111"), 21 | ("Vote Program" , "Vote111111111111111111111111111111111111111"), 22 | ("Stake Config" , "StakeConfig11111111111111111111111111111111"), 23 | ("Sol Program" , "So11111111111111111111111111111111111111112"), 24 | ("Clock Sysvar" , "SysvarC1ock11111111111111111111111111111111"), 25 | ("Epoch Schedule Sysvar" , "SysvarEpochSchedu1e111111111111111111111111"), 26 | ("Fees Sysvar" , "SysvarFees111111111111111111111111111111111"), 27 | ("Last Restart Slog Sysvar" , "SysvarLastRestartS1ot1111111111111111111111"), 28 | ("Recent Blockhashes Sysvar" , "SysvarRecentB1ockHashes11111111111111111111"), 29 | ("Rent Sysvar" , "SysvarRent111111111111111111111111111111111"), 30 | ("Slot Hashes" , "SysvarS1otHashes111111111111111111111111111"), 31 | ("Slot History" , "SysvarS1otHistory11111111111111111111111111"), 32 | ("Stake History" , "SysvarStakeHistory1111111111111111111111111"), 33 | ("MagicBlock System Program" , "Magic11111111111111111111111111111111111111"), 34 | ("MagicBlock Delegation Program" , "DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh"), 35 | ("Luzid Authority" , "LUzidNSiPNjYNkxZcUm5hYHwnWPwsUfh2US1cpWwaBm"), 36 | ] 37 | .into_iter() 38 | .map(|(name, key)| (Pubkey::from_str(key).unwrap(), name)) 39 | .collect(); 40 | } 41 | 42 | pub fn map_instruction( 43 | instruction: &impl ParseableInstruction, 44 | idl: Option<&Idl>, 45 | ) -> InstructionMapResult { 46 | InstructionMapper::map_accounts(instruction, idl) 47 | } 48 | 49 | pub struct InstructionMapper { 50 | idl_instruction: IdlInstruction, 51 | } 52 | 53 | pub struct InstructionMapResult { 54 | pub accounts: HashMap, 55 | pub instruction_name: Option, 56 | pub program_name: Option, 57 | } 58 | 59 | impl InstructionMapper { 60 | /// First determines which IDL to use via the [program_id] of the instruction. 61 | /// Then it finds the best matching IDL instruction for provided instruction and 62 | /// creates an entry for each account pubkey providing its name. 63 | pub fn map_accounts( 64 | instruction: &impl ParseableInstruction, 65 | idl: Option<&Idl>, 66 | ) -> InstructionMapResult { 67 | let mapper = idl 68 | .as_ref() 69 | .and_then(|idl| Self::determine_accounts_mapper(instruction, idl)); 70 | let program_name = idl.as_ref().map(|idl| idl.name.to_string()); 71 | let program_id = instruction.program_id(); 72 | 73 | let mut accounts = HashMap::new(); 74 | let mut instruction_name = None::; 75 | let ix_accounts = instruction.accounts(); 76 | for (idx, pubkey) in ix_accounts.into_iter().enumerate() { 77 | if let Some(name) = BUILTIN_PROGRAMS.get(&pubkey) { 78 | accounts.insert(pubkey, name.to_string()); 79 | continue; 80 | } 81 | if let Some(program_name) = program_name.as_ref() { 82 | if &pubkey == program_id { 83 | accounts.insert(pubkey, program_name.to_string()); 84 | continue; 85 | } 86 | } 87 | if let Some(mapper) = &mapper { 88 | let name = mapper 89 | .idl_instruction 90 | .accounts 91 | .get(idx) 92 | .map(|x| x.name().to_string()); 93 | if let Some(name) = name { 94 | accounts.insert(pubkey, name); 95 | } 96 | instruction_name 97 | .replace(mapper.idl_instruction.name.to_string()); 98 | } 99 | } 100 | let program_name = idl.map(|x| x.name.to_string()).or_else(|| { 101 | BUILTIN_PROGRAMS.get(program_id).map(|x| x.to_string()) 102 | }); 103 | 104 | InstructionMapResult { 105 | accounts, 106 | instruction_name, 107 | program_name, 108 | } 109 | } 110 | 111 | fn determine_accounts_mapper( 112 | instruction: &impl ParseableInstruction, 113 | idl: &Idl, 114 | ) -> Option { 115 | find_best_matching_idl_ix(&idl.instructions, instruction) 116 | .map(|idl_instruction| InstructionMapper { idl_instruction }) 117 | } 118 | } 119 | 120 | fn find_best_matching_idl_ix( 121 | ix_idls: &[IdlInstruction], 122 | ix: &impl ParseableInstruction, 123 | ) -> Option { 124 | let mut best_match = None; 125 | let mut best_match_score = 0; 126 | for idl_ix in ix_idls { 127 | let disc = discriminator_from_ix(idl_ix); 128 | trace!("Discriminator for '{}': {:?}", idl_ix.name, disc); 129 | if disc.len() > ix.data().len() { 130 | continue; 131 | } 132 | let mut score = 0; 133 | for (a, b) in disc.iter().zip(ix.data()) { 134 | if a != b { 135 | break; 136 | } 137 | score += 1; 138 | } 139 | if score > best_match_score { 140 | best_match = Some(idl_ix); 141 | best_match_score = score; 142 | } 143 | } 144 | best_match.cloned() 145 | } 146 | -------------------------------------------------------------------------------- /src/ixs/mod.rs: -------------------------------------------------------------------------------- 1 | use solana_sdk::pubkey::Pubkey; 2 | 3 | mod discriminator; 4 | mod instruction_mapper; 5 | 6 | pub trait ParseableInstruction { 7 | fn program_id(&self) -> &Pubkey; 8 | fn accounts(&self) -> Vec; 9 | fn data(&self) -> &[u8]; 10 | } 11 | 12 | pub use instruction_mapper::{ 13 | map_instruction, InstructionMapResult, InstructionMapper, BUILTIN_PROGRAMS, 14 | }; 15 | -------------------------------------------------------------------------------- /src/json/discriminator.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, fmt::Write}; 2 | 3 | use solana_idl::{Idl, IdlTypeDefinition, IdlTypeDefinitionTy}; 4 | 5 | use crate::{ 6 | deserializer::DeserializeProvider, 7 | discriminator::{ 8 | account_discriminator, match_discriminator::MatchDiscriminators, 9 | DiscriminatorBytes, 10 | }, 11 | errors::{ChainparserError, ChainparserResult}, 12 | idl::IdlProvider, 13 | json::{ 14 | JsonIdlTypeDefinitionDeserializer, JsonSerializationOpts, 15 | JsonTypeDefinitionDeserializerMap, 16 | }, 17 | }; 18 | 19 | // ----------------- 20 | // PrefixDiscriminator 21 | // ----------------- 22 | 23 | /// This is the common way of resolving the account type for account data. 24 | /// It expects the first 8 bytes of data to hold the account discriminator as is the case for 25 | /// anchor accounts. 26 | /// This is what is used for Anchor accounts. 27 | pub struct PrefixDiscriminator<'opts> { 28 | /// Allows looking up a account names by discriminator. 29 | account_names: HashMap, 30 | 31 | /// The deserializers for accounts of this program keyed by the discriminator of each account 32 | /// type. 33 | deserializers: 34 | HashMap>, 35 | 36 | de_provider: DeserializeProvider, 37 | } 38 | 39 | impl<'opts> PrefixDiscriminator<'opts> { 40 | pub fn new( 41 | de_provider: DeserializeProvider, 42 | accounts: &[IdlTypeDefinition], 43 | type_map: JsonTypeDefinitionDeserializerMap<'opts>, 44 | opts: &'opts JsonSerializationOpts, 45 | ) -> Self { 46 | let mut by_name = HashMap::::new(); 47 | let mut deserializers = HashMap::< 48 | DiscriminatorBytes, 49 | JsonIdlTypeDefinitionDeserializer<'opts>, 50 | >::new(); 51 | 52 | for account_definition in accounts { 53 | let type_deserializer = 54 | JsonIdlTypeDefinitionDeserializer::<'opts>::new( 55 | account_definition, 56 | type_map.clone(), 57 | opts, 58 | ); 59 | 60 | // NOTE: for now we assume that one account doesn't reference another 61 | // thus we don't include it in the lookup map for nested types 62 | // Similarly for instruction args once we support them 63 | let discriminator = account_discriminator(&account_definition.name); 64 | deserializers.insert(discriminator, type_deserializer); 65 | by_name.insert(account_definition.name.clone(), discriminator); 66 | } 67 | 68 | let account_names = by_name 69 | .iter() 70 | .map(|(name, discriminator)| (*discriminator, name.clone())) 71 | .collect(); 72 | 73 | Self { 74 | de_provider, 75 | account_names, 76 | deserializers, 77 | } 78 | } 79 | 80 | /// Deserializes 81 | pub fn deserialize_account_data( 82 | &self, 83 | account_data: &mut &[u8], 84 | f: &mut W, 85 | ) -> ChainparserResult<()> { 86 | if account_data.len() < 8 { 87 | return Err( 88 | ChainparserError::AccountDataTooShortForDiscriminatorBytes( 89 | account_data.len(), 90 | 8, 91 | ), 92 | ); 93 | } 94 | let discriminator = &account_data[..8]; 95 | let deserializer = 96 | self.deserializers.get(discriminator).ok_or_else(|| { 97 | ChainparserError::UnknownDiscriminatedAccount(format!( 98 | "disciminator: {discriminator:?}" 99 | )) 100 | })?; 101 | 102 | let data = &mut &account_data[8..]; 103 | deserialize(&self.de_provider, deserializer, f, data) 104 | } 105 | 106 | pub fn deserialize_account_data_by_name( 107 | &self, 108 | account_data: &mut &[u8], 109 | account_name: &str, 110 | f: &mut W, 111 | ) -> ChainparserResult<()> { 112 | let discriminator = account_discriminator(account_name); 113 | let deserializer = 114 | self.deserializers.get(&discriminator).ok_or_else(|| { 115 | ChainparserError::UnknownAccount(account_name.to_string()) 116 | })?; 117 | 118 | deserialize(&self.de_provider, deserializer, f, account_data) 119 | } 120 | 121 | pub fn account_name( 122 | &self, 123 | discriminator: &DiscriminatorBytes, 124 | ) -> Option<&str> { 125 | self.account_names.get(discriminator).map(|s| s.as_str()) 126 | } 127 | } 128 | 129 | // ----------------- 130 | // MatchDiscriminator 131 | // ----------------- 132 | 133 | /// This discriminator is used when no discriminator bytes are added to the account data. 134 | /// It tries as best as possible to match account data to the account type via size and expected 135 | /// patterns in the data. 136 | pub struct MatchDiscriminator<'opts> { 137 | /// Used to match the shape of the account against a given buffer to identify it and provide 138 | /// its name. 139 | discriminators: MatchDiscriminators, 140 | 141 | /// The deserializers for accounts of this program keyed by the name of each account 142 | /// type. 143 | deserializer_by_name: 144 | HashMap>, 145 | 146 | de_provider: DeserializeProvider, 147 | } 148 | 149 | impl<'opts> MatchDiscriminator<'opts> { 150 | pub fn new( 151 | de_provider: DeserializeProvider, 152 | accounts: &[IdlTypeDefinition], 153 | type_map: &HashMap, 154 | type_de_map: JsonTypeDefinitionDeserializerMap<'opts>, 155 | opts: &'opts JsonSerializationOpts, 156 | ) -> Self { 157 | let discriminators = MatchDiscriminators::from((accounts, type_map)); 158 | let mut deserializer_by_name = 159 | HashMap::>::new(); 160 | 161 | for disc in discriminators.iter() { 162 | let deserializer = JsonIdlTypeDefinitionDeserializer::<'opts>::new( 163 | &disc.account, 164 | type_de_map.clone(), 165 | opts, 166 | ); 167 | deserializer_by_name 168 | .insert(disc.account_name().to_string(), deserializer); 169 | } 170 | Self { 171 | de_provider, 172 | discriminators, 173 | deserializer_by_name, 174 | } 175 | } 176 | 177 | pub fn deserialize_account_data( 178 | &self, 179 | account_data: &mut &[u8], 180 | f: &mut W, 181 | ) -> ChainparserResult<()> { 182 | if account_data.is_empty() { 183 | return Err( 184 | ChainparserError::AccountDataTooShortForDiscriminatorBytes( 185 | 0, 1, 186 | ), 187 | ); 188 | } 189 | match self.discriminators.find_match_name(account_data) { 190 | Some(name) => { 191 | self.deserialize_account_data_by_name(account_data, name, f) 192 | } 193 | None => Err(ChainparserError::CannotFindDeserializerForAccount), 194 | } 195 | } 196 | 197 | pub fn deserialize_account_data_by_name( 198 | &self, 199 | account_data: &mut &[u8], 200 | account_name: &str, 201 | f: &mut W, 202 | ) -> ChainparserResult<()> { 203 | match self.deserializer_by_name.get(account_name) { 204 | Some(deserializer) => { 205 | deserialize(&self.de_provider, deserializer, f, account_data) 206 | } 207 | None => { 208 | Err(ChainparserError::UnknownAccount(account_name.to_string())) 209 | } 210 | } 211 | } 212 | 213 | pub fn account_name(&self, account_data: &[u8]) -> Option<&str> { 214 | self.discriminators.find_match_name(account_data) 215 | } 216 | } 217 | 218 | // ----------------- 219 | // JsonAccountsDiscriminator 220 | // ----------------- 221 | pub enum JsonAccountsDiscriminator<'opts> { 222 | PrefixDiscriminator(PrefixDiscriminator<'opts>), 223 | MatchDiscriminator(MatchDiscriminator<'opts>), 224 | } 225 | 226 | impl<'opts> JsonAccountsDiscriminator<'opts> { 227 | pub fn new( 228 | de_provider: DeserializeProvider, 229 | provider: IdlProvider, 230 | idl: &Idl, 231 | type_map: &HashMap, 232 | type_de_map: JsonTypeDefinitionDeserializerMap<'opts>, 233 | opts: &'opts JsonSerializationOpts, 234 | ) -> Self { 235 | match provider { 236 | IdlProvider::Anchor => { 237 | Self::PrefixDiscriminator(PrefixDiscriminator::new( 238 | de_provider, 239 | &idl.accounts, 240 | type_de_map, 241 | opts, 242 | )) 243 | } 244 | _ => Self::MatchDiscriminator(MatchDiscriminator::new( 245 | de_provider, 246 | &idl.accounts, 247 | type_map, 248 | type_de_map, 249 | opts, 250 | )), 251 | } 252 | } 253 | } 254 | 255 | // ----------------- 256 | // Helpers 257 | // ----------------- 258 | fn deserialize( 259 | de_provider: &DeserializeProvider, 260 | deserializer: &JsonIdlTypeDefinitionDeserializer, 261 | f: &mut impl Write, 262 | data: &mut &[u8], 263 | ) -> ChainparserResult<()> { 264 | match de_provider { 265 | DeserializeProvider::Borsh(de) => deserializer.deserialize(de, f, data), 266 | DeserializeProvider::Spl(de) => deserializer.deserialize(de, f, data), 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/json/json_accounts_deserializer.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | fmt::Write, 4 | sync::{Arc, Mutex}, 5 | }; 6 | 7 | use solana_idl::{Idl, IdlTypeDefinitionTy}; 8 | 9 | use super::{ 10 | discriminator::JsonAccountsDiscriminator, JsonTypeDefinitionDeserializerMap, 11 | }; 12 | use crate::{ 13 | deserializer::DeserializeProvider, 14 | discriminator::discriminator_from_data, 15 | errors::ChainparserResult, 16 | idl::IdlProvider, 17 | json::{JsonIdlTypeDefinitionDeserializer, JsonSerializationOpts}, 18 | }; 19 | 20 | /// Setup to deserialize accounts for a given program. The accounts are expected to have been 21 | /// serialized using the [borsh] format. 22 | /// 23 | /// Uses deserializers defined inside [deserializer] modules under the hood in order to resolve the 24 | /// appropriate [borsh] deserializers for each field. 25 | pub struct JsonAccountsDeserializer<'opts> { 26 | /// Used to provide the deserializer for each account by discriminating/matching its data. 27 | pub discriminator: JsonAccountsDiscriminator<'opts>, 28 | 29 | /// The [JsonSerializationOpts] specifying how specific data types should be deserialized. 30 | pub serialization_opts: &'opts JsonSerializationOpts, 31 | 32 | /// Map of [JsonIdlTypeDefinitionDeserializer] for each type defined in the IDL. 33 | pub type_de_map: JsonTypeDefinitionDeserializerMap<'opts>, 34 | } 35 | 36 | impl<'opts> JsonAccountsDeserializer<'opts> { 37 | /// Tries to create an [AccounbtDeserializer] by parsing the [Idl]. 38 | /// Fails if the IDL could not be parsed. 39 | /// 40 | /// - [json} the IDL definition in JSON format 41 | /// - [provider] the provider used to create the IDL 42 | /// - [serialization_opts] specifying how specific data types should be deserialized. 43 | pub fn try_from_idl( 44 | json: &str, 45 | provider: IdlProvider, 46 | serialization_opts: &'opts JsonSerializationOpts, 47 | ) -> ChainparserResult { 48 | let idl: Idl = serde_json::from_str(json)?; 49 | let de_resolver = DeserializeProvider::try_from(&idl)?; 50 | Ok(Self::from_idl( 51 | &idl, 52 | de_resolver, 53 | provider, 54 | serialization_opts, 55 | )) 56 | } 57 | 58 | /// Creates an [AccounbtDeserializer] from the provided [Idl] 59 | /// Fails if the IDL could not be parsed. 60 | /// 61 | /// - [idl} the IDL definition 62 | /// - [de_provider] to be used to deserialize each account, i.e. Borsh 63 | /// - [provider] the provider used to create the IDL 64 | /// - [serialization_opts] specifying how specific data types should be deserialized. 65 | pub fn from_idl( 66 | idl: &Idl, 67 | de_provider: DeserializeProvider, 68 | provider: IdlProvider, 69 | serialization_opts: &'opts JsonSerializationOpts, 70 | ) -> Self { 71 | let type_de_map = Arc::new(Mutex::new(HashMap::new())); 72 | let mut type_map = HashMap::::new(); 73 | 74 | for type_definition in &idl.types { 75 | type_map.insert(type_definition.name.clone(), &type_definition.ty); 76 | let instance = JsonIdlTypeDefinitionDeserializer::new( 77 | type_definition, 78 | type_de_map.clone(), 79 | serialization_opts, 80 | ); 81 | type_de_map 82 | .lock() 83 | .unwrap() 84 | .insert(instance.name.clone(), instance); 85 | } 86 | 87 | let discriminator = JsonAccountsDiscriminator::new( 88 | de_provider, 89 | provider, 90 | idl, 91 | &type_map, 92 | type_de_map.clone(), 93 | serialization_opts, 94 | ); 95 | 96 | Self { 97 | serialization_opts, 98 | discriminator, 99 | type_de_map, 100 | } 101 | } 102 | 103 | /// Deserializes an account from the provided data. 104 | pub fn deserialize_account_data( 105 | &self, 106 | account_data: &mut &[u8], 107 | f: &mut W, 108 | ) -> ChainparserResult<()> { 109 | use JsonAccountsDiscriminator::*; 110 | match &self.discriminator { 111 | PrefixDiscriminator(disc) => { 112 | disc.deserialize_account_data(account_data, f) 113 | } 114 | MatchDiscriminator(disc) => { 115 | disc.deserialize_account_data(account_data, f) 116 | } 117 | } 118 | } 119 | 120 | /// Deserializes an account from the provided data. 121 | /// 122 | /// This method expects account data to **not** be prefixed with 8 bytes of discriminator data. 123 | /// Instead it derives that discriminator from the provided account name and then looks up the 124 | /// json. 125 | pub fn deserialize_account_data_by_name( 126 | &self, 127 | account_data: &mut &[u8], 128 | account_name: &str, 129 | f: &mut W, 130 | ) -> ChainparserResult<()> { 131 | use JsonAccountsDiscriminator::*; 132 | match &self.discriminator { 133 | PrefixDiscriminator(disc) => disc.deserialize_account_data_by_name( 134 | account_data, 135 | account_name, 136 | f, 137 | ), 138 | MatchDiscriminator(disc) => disc.deserialize_account_data_by_name( 139 | account_data, 140 | account_name, 141 | f, 142 | ), 143 | } 144 | } 145 | 146 | /// Resolves the account name for the provided account data. 147 | pub fn account_name(&self, account_data: &[u8]) -> Option<&str> { 148 | use JsonAccountsDiscriminator::*; 149 | match &self.discriminator { 150 | PrefixDiscriminator(disc) => { 151 | if account_data.len() < 8 { 152 | return None; 153 | } 154 | let discriminator = 155 | discriminator_from_data(&account_data[0..8]); 156 | disc.account_name(&discriminator) 157 | } 158 | MatchDiscriminator(disc) => disc.account_name(account_data), 159 | } 160 | } 161 | } 162 | 163 | // The [type_de_map] can hold circular references and thus leaks memory if not cleared. 164 | impl Drop for JsonAccountsDeserializer<'_> { 165 | fn drop(&mut self) { 166 | self.type_de_map.lock().unwrap().clear(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/json/json_common.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | 3 | use super::json_idl_field_de::JsonIdlFieldDeserializer; 4 | use crate::{deserializer::ChainparserDeserialize, errors::ChainparserResult}; 5 | 6 | pub fn deserialize_fields_to_object( 7 | de: &impl ChainparserDeserialize, 8 | f: &mut W, 9 | buf: &mut &[u8], 10 | fields: &[JsonIdlFieldDeserializer<'_>], 11 | ) -> ChainparserResult<()> { 12 | f.write_char('{')?; 13 | 14 | for (i, field_de) in fields.iter().enumerate() { 15 | field_de.deserialize(de, f, buf)?; 16 | if (i + 1) < fields.len() { 17 | f.write_char(',')?; 18 | } 19 | } 20 | 21 | f.write_char('}')?; 22 | 23 | Ok(()) 24 | } 25 | 26 | #[inline(always)] 27 | pub fn write_quoted( 28 | f: &mut W, 29 | s: &str, 30 | ) -> Result<(), std::fmt::Error> { 31 | f.write_str("\"")?; 32 | f.write_str(s)?; 33 | f.write_str("\"") 34 | } 35 | -------------------------------------------------------------------------------- /src/json/json_idl_enum_variant_de.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | 3 | use solana_idl::{EnumFields, IdlEnumVariant, IdlType}; 4 | 5 | use super::{ 6 | json_common::{deserialize_fields_to_object, write_quoted}, 7 | json_idl_field_de::JsonIdlFieldDeserializer, 8 | json_idl_type_de::JsonIdlTypeDeserializer, 9 | JsonTypeDefinitionDeserializerMap, 10 | }; 11 | use crate::{ 12 | deserializer::ChainparserDeserialize, 13 | errors::{ChainparserError, ChainparserResult}, 14 | json::json_serialization_opts::JsonSerializationOpts, 15 | }; 16 | 17 | /// Deserializes an enum variant. 18 | /// It is very similar to the [IdlTypeDefinitionDeserializer] since non-scalar enum variants get 19 | /// treated like a named type with fields. 20 | /// However it handles scalar variants as well as unnamed field variants in a specific way. 21 | #[derive(Clone)] 22 | pub struct JsonIdlEnumVariantDeserializer<'opts> { 23 | pub name: String, 24 | pub named_fields: Option>>, 25 | pub tuple_types: Option<(JsonIdlTypeDeserializer<'opts>, IdlType)>, 26 | pub type_map: JsonTypeDefinitionDeserializerMap<'opts>, 27 | } 28 | 29 | impl<'opts> JsonIdlEnumVariantDeserializer<'opts> { 30 | pub fn new( 31 | variant: &IdlEnumVariant, 32 | type_map: JsonTypeDefinitionDeserializerMap<'opts>, 33 | opts: &'opts JsonSerializationOpts, 34 | ) -> Self { 35 | let name = variant.name.clone(); 36 | use EnumFields::*; 37 | match &variant.fields { 38 | Some(Named(fields)) => { 39 | let named_fields = fields 40 | .iter() 41 | .map(|f| { 42 | JsonIdlFieldDeserializer::new(f, type_map.clone(), opts) 43 | }) 44 | .collect(); 45 | Self { 46 | name, 47 | named_fields: Some(named_fields), 48 | tuple_types: None, 49 | type_map, 50 | } 51 | } 52 | Some(Tuple(types)) => { 53 | let tuple_ty_de = 54 | JsonIdlTypeDeserializer::new(type_map.clone(), opts); 55 | Self { 56 | name, 57 | named_fields: None, 58 | tuple_types: Some(( 59 | tuple_ty_de, 60 | IdlType::Tuple(types.clone()), 61 | )), 62 | type_map, 63 | } 64 | } 65 | None => Self { 66 | name, 67 | named_fields: None, 68 | tuple_types: None, 69 | type_map, 70 | }, 71 | } 72 | } 73 | /// Deserializes the enum variant into JSON that has the same format that [serde_json] uses. 74 | /// This means that non-scalar variants field values are wrapped in an object whose key is the 75 | /// variant name. 76 | /// Scalar variants are just a string of the variant name. 77 | pub fn deserialize( 78 | &self, 79 | de: &impl ChainparserDeserialize, 80 | f: &mut W, 81 | buf: &mut &[u8], 82 | ) -> ChainparserResult<()> { 83 | if let Some(named_fields) = &self.named_fields { 84 | f.write_char('{')?; 85 | { 86 | self.write_key(f)?; 87 | deserialize_fields_to_object(de, f, buf, named_fields) 88 | .map_err(|e| { 89 | ChainparserError::EnumVariantDeserializeError( 90 | self.name.to_string(), 91 | Box::new(e), 92 | ) 93 | })?; 94 | } 95 | f.write_char('}')?; 96 | } else if let Some((tuple_ty_de, ty)) = &self.tuple_types { 97 | f.write_char('{')?; 98 | { 99 | self.write_key(f)?; 100 | self.deserialize_tuple_fields(de, f, buf, tuple_ty_de, ty) 101 | .map_err(|e| { 102 | ChainparserError::EnumVariantDeserializeError( 103 | self.name.to_string(), 104 | Box::new(e), 105 | ) 106 | })?; 107 | } 108 | f.write_char('}')?; 109 | } else { 110 | write_quoted(f, &self.name)?; 111 | } 112 | Ok(()) 113 | } 114 | 115 | fn deserialize_tuple_fields( 116 | &self, 117 | de: &impl ChainparserDeserialize, 118 | f: &mut W, 119 | buf: &mut &[u8], 120 | tuple_el_de: &JsonIdlTypeDeserializer<'opts>, 121 | ty: &IdlType, 122 | ) -> ChainparserResult<()> { 123 | tuple_el_de.deserialize(de, ty, f, buf) 124 | } 125 | 126 | fn write_key(&self, f: &mut W) -> ChainparserResult<()> { 127 | f.write_char('"')?; 128 | f.write_str(&self.name)?; 129 | f.write_str("\":")?; 130 | Ok(()) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/json/json_idl_field_de.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | 3 | use solana_idl::{IdlField, IdlType}; 4 | 5 | use super::{ 6 | json_idl_type_de::JsonIdlTypeDeserializer, 7 | JsonTypeDefinitionDeserializerMap, 8 | }; 9 | use crate::{ 10 | deserializer::ChainparserDeserialize, 11 | errors::{ChainparserError, ChainparserResult}, 12 | json::json_serialization_opts::JsonSerializationOpts, 13 | }; 14 | 15 | #[derive(Clone)] 16 | pub struct JsonIdlFieldDeserializer<'opts> { 17 | pub name: String, 18 | pub ty: IdlType, 19 | pub ty_deserealizer: JsonIdlTypeDeserializer<'opts>, 20 | pub type_map: JsonTypeDefinitionDeserializerMap<'opts>, 21 | } 22 | 23 | impl<'opts> JsonIdlFieldDeserializer<'opts> { 24 | pub fn new( 25 | field: &IdlField, 26 | type_map: JsonTypeDefinitionDeserializerMap<'opts>, 27 | opts: &'opts JsonSerializationOpts, 28 | ) -> Self { 29 | let ty_deserealizer = 30 | JsonIdlTypeDeserializer::new(type_map.clone(), opts); 31 | Self { 32 | name: field.name.clone(), 33 | ty: field.ty.clone(), 34 | ty_deserealizer, 35 | type_map, 36 | } 37 | } 38 | 39 | pub fn deserialize( 40 | &self, 41 | de: &impl ChainparserDeserialize, 42 | f: &mut W, 43 | buf: &mut &[u8], 44 | ) -> ChainparserResult<()> { 45 | f.write_char('"')?; 46 | f.write_str(&self.name)?; 47 | f.write_str("\":")?; 48 | self.ty_deserealizer 49 | .deserialize(de, &self.ty, f, buf) 50 | .map_err(|e| { 51 | ChainparserError::FieldDeserializeError( 52 | self.name.to_string(), 53 | Box::new(e), 54 | ) 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/json/json_idl_type_de.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | 3 | use solana_idl::IdlType; 4 | 5 | use super::{json_common::write_quoted, JsonTypeDefinitionDeserializerMap}; 6 | use crate::{ 7 | deserializer::ChainparserDeserialize, 8 | errors::{ChainparserError, ChainparserResult}, 9 | json::json_serialization_opts::JsonSerializationOpts, 10 | }; 11 | 12 | #[derive(Clone)] 13 | pub struct JsonIdlTypeDeserializer<'opts> { 14 | pub type_map: JsonTypeDefinitionDeserializerMap<'opts>, 15 | pub opts: &'opts JsonSerializationOpts, 16 | } 17 | 18 | impl<'opts> JsonIdlTypeDeserializer<'opts> { 19 | pub fn new( 20 | type_map: JsonTypeDefinitionDeserializerMap<'opts>, 21 | opts: &'opts JsonSerializationOpts, 22 | ) -> Self { 23 | Self { type_map, opts } 24 | } 25 | 26 | pub fn deserialize( 27 | &self, 28 | de: &impl ChainparserDeserialize, 29 | ty: &IdlType, 30 | f: &mut W, 31 | buf: &mut &[u8], 32 | ) -> ChainparserResult<()> { 33 | use IdlType::{ 34 | Bool, F32, F64, I128, I16, I32, I64, I8, U128, U16, U32, U64, U8, 35 | }; 36 | match ty { 37 | U8 => f.write_str(&de.u8(buf)?.to_string()), 38 | U16 => f.write_str(&de.u16(buf)?.to_string()), 39 | U32 => f.write_str(&de.u32(buf)?.to_string()), 40 | U64 if self.opts.n64_as_string => { 41 | write_quoted(f, &de.u64(buf)?.to_string()) 42 | } 43 | U64 => f.write_str(&de.u64(buf)?.to_string()), 44 | 45 | U128 if self.opts.n128_as_string => { 46 | write_quoted(f, &de.u128(buf)?.to_string()) 47 | } 48 | U128 => f.write_str(&de.u128(buf)?.to_string()), 49 | 50 | I8 => f.write_str(&de.i8(buf)?.to_string()), 51 | I16 => f.write_str(&de.i16(buf)?.to_string()), 52 | I32 => f.write_str(&de.i32(buf)?.to_string()), 53 | 54 | I64 if self.opts.n64_as_string => { 55 | write_quoted(f, &de.i64(buf)?.to_string()) 56 | } 57 | I64 => f.write_str(&de.i64(buf)?.to_string()), 58 | 59 | I128 if self.opts.n128_as_string => { 60 | write_quoted(f, &de.i128(buf)?.to_string()) 61 | } 62 | I128 => f.write_str(&de.i128(buf)?.to_string()), 63 | 64 | F32 => f.write_str(&de.f32(buf)?.to_string()), 65 | F64 => f.write_str(&de.f64(buf)?.to_string()), 66 | 67 | Bool => f.write_str(&de.bool(buf)?.to_string()), 68 | 69 | IdlType::String => write_quoted(f, &de.string(buf)?), 70 | 71 | // Composites 72 | IdlType::Tuple(inners) => { 73 | let len = inners.len(); 74 | f.write_char('[')?; 75 | for (i, inner) in inners.iter().enumerate() { 76 | self.deserialize(de, inner, f, buf)?; 77 | if i < len - 1 { 78 | f.write_str(", ")?; 79 | } 80 | } 81 | f.write_char(']') 82 | } 83 | IdlType::Array(inner, len) => { 84 | f.write_char('[')?; 85 | for i in 0..*len { 86 | self.deserialize(de, inner, f, buf).map_err(|e| { 87 | ChainparserError::CompositeDeserializeError( 88 | format!("Array[{i}] size({len})"), 89 | Box::new(e), 90 | ) 91 | })?; 92 | if i < len - 1 { 93 | f.write_str(", ")?; 94 | } 95 | } 96 | f.write_char(']') 97 | } 98 | IdlType::Vec(inner) => { 99 | let len = de.u32(buf)?; 100 | f.write_char('[')?; 101 | for i in 0..len { 102 | self.deserialize(de, inner, f, buf).map_err(|e| { 103 | ChainparserError::CompositeDeserializeError( 104 | format!("Vec[{i}] size({len})"), 105 | Box::new(e), 106 | ) 107 | })?; 108 | if i < len - 1 { 109 | f.write_str(", ")?; 110 | } 111 | } 112 | f.write_char(']') 113 | } 114 | IdlType::HashMap(inner1, inner2) 115 | | IdlType::BTreeMap(inner1, inner2) => { 116 | let len = de.u32(buf)?; 117 | f.write_char('{')?; 118 | for i in 0..len { 119 | f.write_char('"')?; 120 | self.deserialize(de, inner1, f, buf).map_err(|e| { 121 | ChainparserError::CompositeDeserializeError( 122 | format!("Key HashMap[{i}] size({len})"), 123 | Box::new(e), 124 | ) 125 | })?; 126 | f.write_str("\": ")?; 127 | self.deserialize(de, inner2, f, buf).map_err(|e| { 128 | ChainparserError::CompositeDeserializeError( 129 | format!("Val HashMap[{i}] size({len})"), 130 | Box::new(e), 131 | ) 132 | })?; 133 | if i < len - 1 { 134 | f.write_str(", ")?; 135 | } 136 | } 137 | f.write_char('}') 138 | } 139 | IdlType::HashSet(inner) | IdlType::BTreeSet(inner) => { 140 | let len = de.u32(buf)?; 141 | f.write_char('[')?; 142 | for i in 0..len { 143 | self.deserialize(de, inner, f, buf).map_err(|e| { 144 | ChainparserError::CompositeDeserializeError( 145 | format!("HashSet[{i}] size({len})"), 146 | Box::new(e), 147 | ) 148 | })?; 149 | if i < len - 1 { 150 | f.write_str(", ")?; 151 | } 152 | } 153 | f.write_char(']') 154 | } 155 | IdlType::Option(inner) => { 156 | if de.option(buf)? { 157 | self.deserialize(de, inner, f, buf).map_err(|e| { 158 | ChainparserError::CompositeDeserializeError( 159 | "Option".to_string(), 160 | Box::new(e), 161 | ) 162 | })?; 163 | } else { 164 | f.write_str("null")?; 165 | } 166 | Ok(()) 167 | } 168 | IdlType::COption(inner) => { 169 | if de.coption(buf, inner)? { 170 | self.deserialize(de, inner, f, buf).map_err(|e| { 171 | ChainparserError::CompositeDeserializeError( 172 | "Option".to_string(), 173 | Box::new(e), 174 | ) 175 | })?; 176 | } else { 177 | f.write_str("null")?; 178 | } 179 | Ok(()) 180 | } 181 | IdlType::Bytes => { 182 | // Bytes is the same as a u8 array, thus stringify to an array of numbers 183 | // in order to be able to later JSON.parse it back into a bytes array. 184 | f.write_char('[')?; 185 | let bytes = de 186 | .bytes(buf)? 187 | .into_iter() 188 | .map(|b| b.to_string()) 189 | .collect::>() 190 | .join(", "); 191 | f.write_str(&bytes)?; 192 | f.write_char(']') 193 | } 194 | IdlType::PublicKey => { 195 | let pubkey = de.pubkey(buf)?; 196 | if self.opts.pubkey_as_base58 { 197 | write_quoted(f, &pubkey.to_string())?; 198 | } else { 199 | write!(f, "{:?}", pubkey.to_bytes())?; 200 | } 201 | Ok(()) 202 | } 203 | IdlType::Defined(name) => { 204 | let ty = { self.type_map.lock().unwrap().get(name).cloned() }; 205 | match ty { 206 | Some(deser) => { 207 | deser.deserialize(de, f, buf).map_err(|e| { 208 | ChainparserError::CompositeDeserializeError( 209 | format!("Defined('{name}')"), 210 | Box::new(e), 211 | ) 212 | })?; 213 | Ok(()) 214 | } 215 | None => Err(ChainparserError::CannotFindDefinedType( 216 | name.to_string(), 217 | ))?, 218 | } 219 | } 220 | }?; 221 | Ok(()) 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/json/json_idl_type_def_de.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | 3 | use borsh::BorshDeserialize; 4 | use solana_idl::{IdlTypeDefinition, IdlTypeDefinitionTy}; 5 | 6 | use super::{ 7 | json_common::deserialize_fields_to_object, 8 | json_idl_enum_variant_de::JsonIdlEnumVariantDeserializer, 9 | json_idl_field_de::JsonIdlFieldDeserializer, 10 | JsonTypeDefinitionDeserializerMap, 11 | }; 12 | use crate::{ 13 | deserializer::ChainparserDeserialize, 14 | errors::{ChainparserError, ChainparserResult}, 15 | json::json_serialization_opts::JsonSerializationOpts, 16 | }; 17 | 18 | #[derive(Clone)] 19 | pub struct JsonIdlTypeDefinitionDeserializer<'opts> { 20 | pub name: String, 21 | pub fields: Option>>, 22 | pub variants: Option>>, 23 | pub type_map: JsonTypeDefinitionDeserializerMap<'opts>, 24 | } 25 | 26 | impl<'opts> JsonIdlTypeDefinitionDeserializer<'opts> { 27 | pub fn new( 28 | definition: &IdlTypeDefinition, 29 | type_map: JsonTypeDefinitionDeserializerMap<'opts>, 30 | opts: &'opts JsonSerializationOpts, 31 | ) -> Self { 32 | match &definition.ty { 33 | IdlTypeDefinitionTy::Struct { fields } => { 34 | let fields = fields 35 | .iter() 36 | .map(|f| { 37 | JsonIdlFieldDeserializer::new(f, type_map.clone(), opts) 38 | }) 39 | .collect(); 40 | Self { 41 | name: definition.name.clone(), 42 | fields: Some(fields), 43 | variants: None, 44 | type_map, 45 | } 46 | } 47 | IdlTypeDefinitionTy::Enum { variants } => { 48 | let variants = variants 49 | .iter() 50 | .map(|v| { 51 | JsonIdlEnumVariantDeserializer::new( 52 | v, 53 | type_map.clone(), 54 | opts, 55 | ) 56 | }) 57 | .collect(); 58 | Self { 59 | name: definition.name.clone(), 60 | fields: None, 61 | variants: Some(variants), 62 | type_map, 63 | } 64 | } 65 | } 66 | } 67 | 68 | pub fn deserialize( 69 | &self, 70 | de: &impl ChainparserDeserialize, 71 | f: &mut W, 72 | buf: &mut &[u8], 73 | ) -> ChainparserResult<()> { 74 | if let Some(fields) = &self.fields { 75 | // Struct 76 | deserialize_fields_to_object(de, f, buf, fields).map_err(|e| { 77 | ChainparserError::StructDeserializeError( 78 | self.name.to_string(), 79 | Box::new(e), 80 | ) 81 | }) 82 | } else { 83 | // Enum 84 | let variants = self 85 | .variants 86 | .as_ref() 87 | .expect("Should either have struct fields or enum variants"); 88 | 89 | // NOTE: not handling enums whose variants start at non-zero discriminant 90 | // if shank/anchor ever supports that, we'll need to handle it here 91 | let discriminator = u8::deserialize(buf)?; 92 | match &variants.get(discriminator as usize) { 93 | Some(deser) => deser.deserialize(de, f, buf), 94 | None => { 95 | Err(ChainparserError::InvalidEnumVariantDiscriminator( 96 | discriminator, 97 | ))? 98 | } 99 | } 100 | .map_err(|e| { 101 | ChainparserError::EnumDeserializeError( 102 | self.name.to_string(), 103 | Box::new(e), 104 | ) 105 | }) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/json/json_serialization_opts.rs: -------------------------------------------------------------------------------- 1 | pub struct JsonSerializationOpts { 2 | pub pubkey_as_base58: bool, 3 | pub n64_as_string: bool, 4 | pub n128_as_string: bool, 5 | } 6 | 7 | impl Default for JsonSerializationOpts { 8 | fn default() -> Self { 9 | Self { 10 | pubkey_as_base58: true, 11 | n64_as_string: false, 12 | n128_as_string: false, 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/json/mod.rs: -------------------------------------------------------------------------------- 1 | mod discriminator; 2 | mod json_accounts_deserializer; 3 | mod json_common; 4 | mod json_idl_enum_variant_de; 5 | mod json_idl_field_de; 6 | mod json_idl_type_de; 7 | mod json_idl_type_def_de; 8 | mod json_serialization_opts; 9 | 10 | use std::{ 11 | collections::HashMap, 12 | sync::{Arc, Mutex}, 13 | }; 14 | 15 | pub use discriminator::PrefixDiscriminator; 16 | pub use json_accounts_deserializer::JsonAccountsDeserializer; 17 | pub use json_idl_type_def_de::JsonIdlTypeDefinitionDeserializer; 18 | pub use json_serialization_opts::JsonSerializationOpts; 19 | 20 | pub type JsonTypeDefinitionDeserializerMap<'opts> = 21 | Arc>>>; 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod api; 2 | mod deserializer; 3 | pub mod errors; 4 | pub mod ixs; 5 | pub mod json; 6 | 7 | pub mod discriminator; 8 | pub mod idl; 9 | 10 | pub use api::*; 11 | pub use deserializer::*; 12 | 13 | pub mod de; 14 | pub mod traits; 15 | 16 | pub use solana_idl::*; 17 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use solana_sdk::{account::Account, pubkey::Pubkey}; 2 | 3 | pub trait AccountProvider { 4 | fn get_account(&self, pubkey: &Pubkey) -> Option<(Account, u64)>; 5 | } 6 | -------------------------------------------------------------------------------- /tests/deserialize_account.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use borsh::BorshSerialize; 4 | use serde::{Deserialize, Serialize}; 5 | use solana_idl::{ 6 | EnumFields, IdlEnumVariant, IdlType, IdlTypeDefinition, IdlTypeDefinitionTy, 7 | }; 8 | use solana_sdk::pubkey::Pubkey; 9 | 10 | mod utils; 11 | pub use chainparser::{ 12 | de::{ 13 | i128_from_string, i64_from_string, opt_pubkey_from_base58, 14 | pubkey_from_base58, u128_from_string, u64_from_string, 15 | vec_pubkey_from_base58, 16 | }, 17 | json::JsonSerializationOpts, 18 | }; 19 | 20 | use crate::utils::{ 21 | process_test_case_json, process_test_case_json_compare_str, to_if, 22 | }; 23 | 24 | #[test] 25 | fn deserialize_struct_with_floats() { 26 | let ty_name = "Floats"; 27 | 28 | #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize)] 29 | pub struct Floats { 30 | pub float_32: f32, 31 | pub float_64: f64, 32 | } 33 | 34 | fn approx_equal(a: f64, b: f64, dp: u8) -> bool { 35 | let p = 10f64.powi(-(dp as i32)); 36 | (a - b).abs() < p 37 | } 38 | 39 | impl PartialEq for Floats { 40 | fn eq(&self, other: &Self) -> bool { 41 | approx_equal(self.float_32 as f64, other.float_32 as f64, 1) 42 | && approx_equal(self.float_64, other.float_64, 1) 43 | } 44 | } 45 | impl Eq for Floats {} 46 | 47 | let idl_type_def = IdlTypeDefinition { 48 | name: ty_name.to_string(), 49 | ty: IdlTypeDefinitionTy::Struct { 50 | fields: vec![ 51 | to_if("float_32", IdlType::F32), 52 | to_if("float_64", IdlType::F64), 53 | ], 54 | }, 55 | }; 56 | 57 | let t = "Positive Floats Case"; 58 | { 59 | let instance = Floats { 60 | // Basically encoding the rounding error here. 61 | // We had 1.1 but then the JSON serializer turned it into 1.100000023841858 and 62 | // our test which compares strings failed 63 | #[allow(clippy::excessive_precision)] 64 | float_32: 1.100000023841858, 65 | float_64: 3.40282348e+38, 66 | }; 67 | 68 | let mut writer = String::new(); 69 | process_test_case_json( 70 | t, 71 | &[&idl_type_def], 72 | instance.clone(), 73 | ty_name, 74 | &mut writer, 75 | None, 76 | None, 77 | ) 78 | } 79 | 80 | let t = "Negative Floats Case"; 81 | { 82 | let instance = Floats { 83 | // Basically encoding the rounding error here. 84 | // We had 1.1 but then the JSON serializer turned it into 1.100000023841858 and 85 | // our test which compares strings failed 86 | #[allow(clippy::excessive_precision)] 87 | float_32: -1.100000023841858, 88 | float_64: -3.40282348e+38, 89 | }; 90 | 91 | let mut writer = String::new(); 92 | process_test_case_json( 93 | t, 94 | &[&idl_type_def], 95 | instance.clone(), 96 | ty_name, 97 | &mut writer, 98 | None, 99 | None, 100 | ) 101 | } 102 | 103 | let t = "NAN Floats Case"; 104 | { 105 | let expected = r#"{ "float_32": NaN,"float_64": NaN }"# 106 | .to_string() 107 | .split_whitespace() 108 | .collect::(); 109 | 110 | let mut writer = String::new(); 111 | process_test_case_json_compare_str( 112 | t, 113 | &[&idl_type_def], 114 | ty_name, 115 | &mut writer, 116 | None, 117 | // f32:NAN f64:NAN 118 | vec![0, 0, 0x01, 0xFF, 0, 0, 0, 0, 0, 0, 0xF8, 0xFF], 119 | &expected, 120 | ) 121 | } 122 | } 123 | 124 | #[test] 125 | fn deserialize_struct_with_composites() { 126 | let ty_name = "Person"; 127 | 128 | #[derive( 129 | Clone, Debug, Serialize, Deserialize, BorshSerialize, Eq, PartialEq, 130 | )] 131 | pub struct Person { 132 | pub name: String, 133 | pub age: u64, 134 | pub ns: Vec, 135 | pub map: HashMap, 136 | pub set: HashSet, 137 | pub opt: Option, 138 | pub bytes: Vec, 139 | pub tuple: (u64, String, Option), 140 | pub vec_tuple: Vec<(u64, String)>, 141 | #[serde(deserialize_with = "pubkey_from_base58")] 142 | pub pubkey: Pubkey, 143 | } 144 | 145 | let idl_type_def = IdlTypeDefinition { 146 | name: ty_name.to_string(), 147 | ty: IdlTypeDefinitionTy::Struct { 148 | fields: vec![ 149 | to_if("name", IdlType::String), 150 | to_if("age", IdlType::U64), 151 | to_if("ns", IdlType::Vec(Box::new(IdlType::I16))), 152 | to_if( 153 | "map", 154 | IdlType::HashMap( 155 | Box::new(IdlType::U8), 156 | Box::new(IdlType::String), 157 | ), 158 | ), 159 | to_if("set", IdlType::HashSet(Box::new(IdlType::String))), 160 | to_if("opt", IdlType::Option(Box::new(IdlType::U64))), 161 | to_if("bytes", IdlType::Bytes), 162 | to_if( 163 | "tuple", 164 | IdlType::Tuple(vec![ 165 | IdlType::U64, 166 | IdlType::String, 167 | IdlType::Option(Box::new(IdlType::U8)), 168 | ]), 169 | ), 170 | to_if( 171 | "vec_tuple", 172 | IdlType::Vec(Box::new(IdlType::Tuple(vec![ 173 | IdlType::U64, 174 | IdlType::String, 175 | ]))), 176 | ), 177 | to_if("pubkey", IdlType::PublicKey), 178 | ], 179 | }, 180 | }; 181 | 182 | let t = "Typical Case with all Composites Populated"; 183 | { 184 | let instance = Person { 185 | name: "John".to_string(), 186 | age: 30, 187 | ns: vec![1, 2, -3], 188 | map: vec![(1, "foo".to_string()), (3, "bar".to_string())] 189 | .into_iter() 190 | .collect(), 191 | set: vec!["uno".to_string(), "dos".to_string()] 192 | .into_iter() 193 | .collect(), 194 | opt: Some(42), 195 | bytes: vec![1, 2, 3, 4, 5, 6, 7, 8], 196 | tuple: (42, "foo".to_string(), Some(3)), 197 | vec_tuple: vec![(1, "foo".to_string()), (3, "bar".to_string())], 198 | pubkey: Pubkey::new_unique(), 199 | }; 200 | 201 | let mut writer = String::new(); 202 | process_test_case_json( 203 | t, 204 | &[&idl_type_def], 205 | instance.clone(), 206 | ty_name, 207 | &mut writer, 208 | None, 209 | None, 210 | ) 211 | } 212 | 213 | let t = "Empty Composites"; 214 | { 215 | let instance = Person { 216 | name: "John".to_string(), 217 | age: 30, 218 | ns: vec![], 219 | map: HashMap::new(), 220 | set: HashSet::new(), 221 | opt: None, 222 | bytes: Vec::new(), 223 | tuple: (0, "".to_string(), None), 224 | vec_tuple: vec![], 225 | pubkey: Pubkey::default(), 226 | }; 227 | let mut writer = String::new(); 228 | process_test_case_json( 229 | t, 230 | &[&idl_type_def], 231 | instance.clone(), 232 | ty_name, 233 | &mut writer, 234 | None, 235 | None, 236 | ) 237 | } 238 | } 239 | 240 | #[test] 241 | fn deserialize_large_nums() { 242 | let ty_name = "Primitives"; 243 | let idl_type_def = IdlTypeDefinition { 244 | name: ty_name.to_string(), 245 | ty: IdlTypeDefinitionTy::Struct { 246 | fields: vec![ 247 | to_if("large_unsigned", IdlType::U64), 248 | to_if("large_signed", IdlType::I64), 249 | to_if("very_large_unsigned", IdlType::U128), 250 | to_if("very_large_signed", IdlType::I128), 251 | ], 252 | }, 253 | }; 254 | 255 | let t = "Default Opts"; 256 | { 257 | #[derive(Clone, Debug, Deserialize, BorshSerialize, Eq, PartialEq)] 258 | pub struct Primitives { 259 | large_unsigned: u64, 260 | large_signed: i64, 261 | very_large_unsigned: u128, 262 | very_large_signed: i128, 263 | } 264 | let instance = Primitives { 265 | large_unsigned: u64::MAX, 266 | large_signed: i64::MIN, 267 | very_large_unsigned: u128::MAX, 268 | very_large_signed: i128::MIN, 269 | }; 270 | let mut writer = String::new(); 271 | process_test_case_json( 272 | t, 273 | &[&idl_type_def], 274 | instance.clone(), 275 | ty_name, 276 | &mut writer, 277 | None, 278 | None, 279 | ); 280 | } 281 | 282 | // The below two only make a difference for JSON 283 | let t = "Opts to not stringify u64/i64"; 284 | #[derive(Debug, Deserialize, BorshSerialize, Eq, PartialEq)] 285 | pub struct Primitives { 286 | #[serde(deserialize_with = "u64_from_string")] 287 | large_unsigned: u64, 288 | #[serde(deserialize_with = "i64_from_string")] 289 | large_signed: i64, 290 | 291 | very_large_unsigned: u128, 292 | very_large_signed: i128, 293 | } 294 | let instance = Primitives { 295 | large_unsigned: u64::MAX, 296 | large_signed: i64::MIN, 297 | very_large_unsigned: u128::MAX, 298 | very_large_signed: i128::MIN, 299 | }; 300 | { 301 | let mut writer = String::new(); 302 | process_test_case_json( 303 | t, 304 | &[&idl_type_def], 305 | instance, 306 | ty_name, 307 | &mut writer, 308 | Some(JsonSerializationOpts { 309 | n64_as_string: true, 310 | ..Default::default() 311 | }), 312 | None, 313 | ); 314 | } 315 | } 316 | 317 | #[test] 318 | fn deserialize_pubkeys() { 319 | let ty_name = "Pubkeys"; 320 | let idl_type_def = IdlTypeDefinition { 321 | name: ty_name.to_string(), 322 | ty: IdlTypeDefinitionTy::Struct { 323 | fields: vec![ 324 | to_if("pubkey", IdlType::PublicKey), 325 | to_if("pubkey_vec", IdlType::Vec(Box::new(IdlType::PublicKey))), 326 | to_if( 327 | "pubkey_opt", 328 | IdlType::Option(Box::new(IdlType::PublicKey)), 329 | ), 330 | ], 331 | }, 332 | }; 333 | 334 | let t = "Opts to not stringify Pubkey"; 335 | { 336 | #[derive(Clone, Debug, Deserialize, BorshSerialize, Eq, PartialEq)] 337 | pub struct Pubkeys { 338 | pubkey: Pubkey, 339 | pubkey_vec: Vec, 340 | pubkey_opt: Option, 341 | } 342 | let instance = Pubkeys { 343 | pubkey: Pubkey::new_unique(), 344 | pubkey_vec: vec![Pubkey::new_unique(), Pubkey::new_unique()], 345 | pubkey_opt: Some(Pubkey::new_unique()), 346 | }; 347 | let mut writer = String::new(); 348 | process_test_case_json( 349 | t, 350 | &[&idl_type_def], 351 | instance.clone(), 352 | ty_name, 353 | &mut writer, 354 | Some(JsonSerializationOpts { 355 | pubkey_as_base58: false, 356 | ..Default::default() 357 | }), 358 | None, 359 | ); 360 | } 361 | 362 | let t = "Default opts"; 363 | { 364 | #[derive(Clone, Debug, Deserialize, BorshSerialize, Eq, PartialEq)] 365 | pub struct Pubkeys { 366 | #[serde(deserialize_with = "pubkey_from_base58")] 367 | pubkey: Pubkey, 368 | #[serde(deserialize_with = "vec_pubkey_from_base58")] 369 | pubkey_vec: Vec, 370 | #[serde(deserialize_with = "opt_pubkey_from_base58")] 371 | pubkey_opt: Option, 372 | } 373 | let instance = Pubkeys { 374 | pubkey: Pubkey::new_unique(), 375 | pubkey_vec: vec![Pubkey::new_unique(), Pubkey::new_unique()], 376 | pubkey_opt: Some(Pubkey::new_unique()), 377 | }; 378 | let mut writer = String::new(); 379 | process_test_case_json( 380 | t, 381 | &[&idl_type_def], 382 | instance.clone(), 383 | ty_name, 384 | &mut writer, 385 | None, 386 | None, 387 | ); 388 | } 389 | } 390 | 391 | #[test] 392 | fn deserialize_nested_types() { 393 | // ----------------- 394 | // Types and Definitions 395 | // ----------------- 396 | 397 | // TypeUno 398 | let ty_uno = "TypeUno"; 399 | #[derive(Clone, Debug, Deserialize, BorshSerialize, Eq, PartialEq)] 400 | pub struct TypeUno { 401 | key: String, 402 | value: u64, 403 | } 404 | let itd_uno = IdlTypeDefinition { 405 | name: ty_uno.to_string(), 406 | ty: IdlTypeDefinitionTy::Struct { 407 | fields: vec![ 408 | to_if("key", IdlType::String), 409 | to_if("value", IdlType::U64), 410 | ], 411 | }, 412 | }; 413 | 414 | // TypeDos 415 | let ty_dos = "TypeDos"; 416 | #[derive(Clone, Debug, Deserialize, BorshSerialize, Eq, PartialEq)] 417 | pub struct TypeDos { 418 | key: u8, 419 | value: String, 420 | } 421 | let itd_dos = IdlTypeDefinition { 422 | name: ty_dos.to_string(), 423 | ty: IdlTypeDefinitionTy::Struct { 424 | fields: vec![ 425 | to_if("key", IdlType::U8), 426 | to_if("value", IdlType::String), 427 | ], 428 | }, 429 | }; 430 | 431 | // NestOneLevelSimple 432 | let ty_nest_one_level_simple = "NestOneLevelSimple"; 433 | #[derive(Clone, Debug, Deserialize, BorshSerialize, Eq, PartialEq)] 434 | pub struct NestOneLevelSimple { 435 | key: String, 436 | uno: TypeUno, 437 | dos: TypeDos, 438 | } 439 | let itd_nest_one_level_simple = IdlTypeDefinition { 440 | name: ty_nest_one_level_simple.to_string(), 441 | ty: IdlTypeDefinitionTy::Struct { 442 | fields: vec![ 443 | to_if("key", IdlType::String), 444 | to_if("uno", IdlType::Defined(ty_uno.to_string())), 445 | to_if("dos", IdlType::Defined(ty_dos.to_string())), 446 | ], 447 | }, 448 | }; 449 | 450 | // NestOneLevelComposite 451 | let ty_nest_one_level_composite = "NestOneLevelComposite"; 452 | #[derive(Clone, Debug, Deserialize, BorshSerialize, Eq, PartialEq)] 453 | pub struct NestOneLevelComposite { 454 | key: String, 455 | uno: HashMap, 456 | dos: Vec, 457 | } 458 | let itd_nest_one_level_composite = IdlTypeDefinition { 459 | name: ty_nest_one_level_composite.to_string(), 460 | ty: IdlTypeDefinitionTy::Struct { 461 | fields: vec![ 462 | to_if("key", IdlType::String), 463 | to_if( 464 | "uno", 465 | IdlType::HashMap( 466 | Box::new(IdlType::U8), 467 | Box::new(IdlType::Defined(ty_uno.to_string())), 468 | ), 469 | ), 470 | to_if( 471 | "dos", 472 | IdlType::Vec(Box::new(IdlType::Defined( 473 | ty_dos.to_string(), 474 | ))), 475 | ), 476 | ], 477 | }, 478 | }; 479 | 480 | // Nest Two Levels 481 | let ty_nest_two_levels = "NestTwoLevels"; 482 | #[derive(Clone, Debug, Deserialize, BorshSerialize, Eq, PartialEq)] 483 | pub struct NestTwoLevels { 484 | key: String, 485 | simple: NestOneLevelSimple, 486 | composite: NestOneLevelComposite, 487 | } 488 | let itd_nest_two_levels = IdlTypeDefinition { 489 | name: ty_nest_two_levels.to_string(), 490 | ty: IdlTypeDefinitionTy::Struct { 491 | fields: vec![ 492 | to_if("key", IdlType::String), 493 | to_if( 494 | "simple", 495 | IdlType::Defined(ty_nest_one_level_simple.to_string()), 496 | ), 497 | to_if( 498 | "composite", 499 | IdlType::Defined(ty_nest_one_level_composite.to_string()), 500 | ), 501 | ], 502 | }, 503 | }; 504 | 505 | let idl_type_defs = [ 506 | &itd_uno, 507 | &itd_dos, 508 | &itd_nest_one_level_simple, 509 | &itd_nest_one_level_composite, 510 | &itd_nest_two_levels, 511 | ]; 512 | 513 | // ----------------- 514 | // Tests 515 | // ----------------- 516 | let t = "NestOneLevelSimple"; 517 | { 518 | let instance = NestOneLevelSimple { 519 | key: "simple".to_string(), 520 | uno: TypeUno { 521 | key: "uno".to_string(), 522 | value: 1, 523 | }, 524 | dos: TypeDos { 525 | key: 2, 526 | value: "dos".to_string(), 527 | }, 528 | }; 529 | 530 | let mut writer = String::new(); 531 | process_test_case_json( 532 | t, 533 | &idl_type_defs, 534 | instance.clone(), 535 | ty_nest_one_level_simple, 536 | &mut writer, 537 | None, 538 | None, 539 | ); 540 | } 541 | let t = "NestOneLevelComposite"; 542 | { 543 | let instance = NestOneLevelComposite { 544 | key: "composite:key".to_string(), 545 | uno: vec![ 546 | ( 547 | 1, 548 | TypeUno { 549 | key: "uno:1".to_string(), 550 | value: 1, 551 | }, 552 | ), 553 | ( 554 | 2, 555 | TypeUno { 556 | key: "uno:2".to_string(), 557 | value: 2, 558 | }, 559 | ), 560 | ] 561 | .into_iter() 562 | .collect(), 563 | dos: vec![ 564 | TypeDos { 565 | key: 1, 566 | value: "dos:1".to_string(), 567 | }, 568 | TypeDos { 569 | key: 2, 570 | value: "dos:2".to_string(), 571 | }, 572 | ], 573 | }; 574 | 575 | let mut writer = String::new(); 576 | process_test_case_json( 577 | t, 578 | &idl_type_defs, 579 | instance.clone(), 580 | ty_nest_one_level_composite, 581 | &mut writer, 582 | None, 583 | None, 584 | ); 585 | } 586 | 587 | let t = "NestTwoLevels"; 588 | { 589 | let instance = NestTwoLevels { 590 | key: "nest:two:levels".to_string(), 591 | simple: NestOneLevelSimple { 592 | key: "simple".to_string(), 593 | uno: TypeUno { 594 | key: "uno".to_string(), 595 | value: 1, 596 | }, 597 | dos: TypeDos { 598 | key: 2, 599 | value: "dos".to_string(), 600 | }, 601 | }, 602 | composite: NestOneLevelComposite { 603 | key: "composite:key".to_string(), 604 | uno: vec![ 605 | ( 606 | 1, 607 | TypeUno { 608 | key: "uno:1".to_string(), 609 | value: 1, 610 | }, 611 | ), 612 | ( 613 | 2, 614 | TypeUno { 615 | key: "uno:2".to_string(), 616 | value: 2, 617 | }, 618 | ), 619 | ] 620 | .into_iter() 621 | .collect(), 622 | dos: vec![ 623 | TypeDos { 624 | key: 1, 625 | value: "dos:1".to_string(), 626 | }, 627 | TypeDos { 628 | key: 2, 629 | value: "dos:2".to_string(), 630 | }, 631 | ], 632 | }, 633 | }; 634 | 635 | let mut writer = String::new(); 636 | process_test_case_json( 637 | t, 638 | &idl_type_defs, 639 | instance.clone(), 640 | ty_nest_two_levels, 641 | &mut writer, 642 | None, 643 | None, 644 | ); 645 | } 646 | } 647 | 648 | #[test] 649 | fn deserialize_mixed_enum() { 650 | let ty_mixed_enum = "MixedEnum"; 651 | #[derive( 652 | Clone, Debug, Serialize, Deserialize, BorshSerialize, Eq, PartialEq, 653 | )] 654 | enum MixedEnum { 655 | Scalar, 656 | NamedFields { uno: u8, dos: u8 }, 657 | UnnamedFields(u8, HashMap), 658 | } 659 | 660 | let itd_mixed_enum = IdlTypeDefinition { 661 | name: ty_mixed_enum.to_string(), 662 | ty: IdlTypeDefinitionTy::Enum { 663 | variants: vec![ 664 | IdlEnumVariant { 665 | name: "Scalar".to_string(), 666 | fields: None, 667 | }, 668 | IdlEnumVariant { 669 | name: "NamedFields".to_string(), 670 | fields: Some(EnumFields::Named(vec![ 671 | to_if("uno", IdlType::U8), 672 | to_if("dos", IdlType::U8), 673 | ])), 674 | }, 675 | IdlEnumVariant { 676 | name: "UnnamedFields".to_string(), 677 | fields: Some(EnumFields::Tuple(vec![ 678 | IdlType::U8, 679 | IdlType::HashMap( 680 | Box::new(IdlType::U8), 681 | Box::new(IdlType::String), 682 | ), 683 | ])), 684 | }, 685 | ], 686 | }, 687 | }; 688 | 689 | let ty_has_mixed_enums = "HasMixedEnums"; 690 | #[derive( 691 | Clone, Debug, Serialize, Deserialize, BorshSerialize, Eq, PartialEq, 692 | )] 693 | struct HasMixedEnums { 694 | key: String, 695 | scalar: MixedEnum, 696 | named_fields: MixedEnum, 697 | unnamed_fields: MixedEnum, 698 | } 699 | 700 | let itd_has_mixed_enums = IdlTypeDefinition { 701 | name: ty_has_mixed_enums.to_string(), 702 | ty: IdlTypeDefinitionTy::Struct { 703 | fields: vec![ 704 | to_if("key", IdlType::String), 705 | to_if("scalar", IdlType::Defined(ty_mixed_enum.to_string())), 706 | to_if( 707 | "named_fields", 708 | IdlType::Defined(ty_mixed_enum.to_string()), 709 | ), 710 | to_if( 711 | "unnamed_fields", 712 | IdlType::Defined(ty_mixed_enum.to_string()), 713 | ), 714 | ], 715 | }, 716 | }; 717 | 718 | let instance = HasMixedEnums { 719 | key: "has:mixed:enums".to_string(), 720 | scalar: MixedEnum::Scalar, 721 | named_fields: MixedEnum::NamedFields { uno: 1, dos: 2 }, 722 | unnamed_fields: MixedEnum::UnnamedFields( 723 | 3, 724 | vec![(4, "four".to_string())].into_iter().collect(), 725 | ), 726 | }; 727 | let idl_type_defs = [&itd_mixed_enum, &itd_has_mixed_enums]; 728 | 729 | let t = "ScalarEnum"; 730 | let mut writer = String::new(); 731 | process_test_case_json( 732 | t, 733 | &idl_type_defs, 734 | instance.clone(), 735 | ty_has_mixed_enums, 736 | &mut writer, 737 | None, 738 | None, 739 | ); 740 | } 741 | -------------------------------------------------------------------------------- /tests/utils/deserialization.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | sync::{Arc, Mutex}, 4 | }; 5 | 6 | use borsh::BorshSerialize; 7 | pub use chainparser::json::{ 8 | JsonIdlTypeDefinitionDeserializer, JsonSerializationOpts, 9 | }; 10 | use serde::Deserialize; 11 | use solana_idl::{IdlField, IdlType, IdlTypeDefinition}; 12 | 13 | pub fn to_if(name: &str, ty: IdlType) -> IdlField { 14 | IdlField { 15 | name: name.to_string(), 16 | ty, 17 | attrs: None, 18 | } 19 | } 20 | 21 | pub fn process_test_case_json<'de, 'a, T>( 22 | label: &str, 23 | idl_type_defs: &[&IdlTypeDefinition], 24 | instance: T, 25 | deser_key: &str, 26 | writer: &'de mut String, 27 | opts: Option, 28 | buf: Option>, 29 | ) where 30 | T: Deserialize<'de> + BorshSerialize + std::fmt::Debug + Eq + PartialEq, 31 | { 32 | let type_map = Arc::new(Mutex::new(HashMap::new())); 33 | let opts = opts.unwrap_or_default(); 34 | 35 | // 1. process all idl type defs to populate the type map and then use 36 | for idl_type_def in idl_type_defs { 37 | let deser = JsonIdlTypeDefinitionDeserializer::new( 38 | idl_type_def, 39 | type_map.clone(), 40 | &opts, 41 | ); 42 | type_map 43 | .lock() 44 | .unwrap() 45 | .insert(idl_type_def.name.clone(), deser); 46 | } 47 | 48 | // 2. deserialize the instance with the requested json 49 | let buf = buf.unwrap_or_else(|| instance.try_to_vec().unwrap()); 50 | 51 | let deser = { 52 | type_map 53 | .lock() 54 | .unwrap() 55 | .get(deser_key) 56 | .cloned() 57 | .unwrap_or_else(|| panic!("Unable to find json {deser_key}")) 58 | }; 59 | let de = chainparser::borsh::BorshDeserializer; 60 | deser 61 | .deserialize(&de, writer, &mut &buf[..]) 62 | .expect("Failed to deserialize"); 63 | 64 | let res = match serde_json::from_str::(writer) { 65 | Ok(res) => res, 66 | Err(e) => { 67 | eprintln!("JSON: {writer}"); 68 | panic!("Failed to deserialize: {e}") 69 | } 70 | }; 71 | if res != instance { 72 | eprintln!("JSON: {writer}"); 73 | } 74 | assert_eq!(res, instance, "{label}"); 75 | } 76 | 77 | pub fn process_test_case_json_compare_str( 78 | label: &str, 79 | idl_type_defs: &[&IdlTypeDefinition], 80 | deser_key: &str, 81 | writer: &mut String, 82 | opts: Option, 83 | buf: Vec, 84 | expected: &str, 85 | ) { 86 | let type_map = Arc::new(Mutex::new(HashMap::new())); 87 | let opts = opts.unwrap_or_default(); 88 | 89 | // 1. process all idl type defs to populate the type map and then use 90 | for idl_type_def in idl_type_defs { 91 | let deser = JsonIdlTypeDefinitionDeserializer::new( 92 | idl_type_def, 93 | type_map.clone(), 94 | &opts, 95 | ); 96 | type_map 97 | .lock() 98 | .unwrap() 99 | .insert(idl_type_def.name.clone(), deser); 100 | } 101 | 102 | let deser = { 103 | type_map 104 | .lock() 105 | .unwrap() 106 | .get(deser_key) 107 | .cloned() 108 | .unwrap_or_else(|| panic!("Unable to find json {deser_key}")) 109 | }; 110 | let de = chainparser::borsh::BorshDeserializer; 111 | deser 112 | .deserialize(&de, writer, &mut &buf[..]) 113 | .expect("Failed to deserialize"); 114 | 115 | assert_eq!(writer, expected, "{label}"); 116 | } 117 | -------------------------------------------------------------------------------- /tests/utils/mod.rs: -------------------------------------------------------------------------------- 1 | mod deserialization; 2 | pub use deserialization::*; 3 | --------------------------------------------------------------------------------