├── .github └── workflows │ ├── node.js.yml │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── __tests__ ├── _common.mjs ├── evm_utils.did.js ├── proof.json ├── rlp.mjs ├── transaction.mjs └── tree.mjs ├── build.sh ├── candid └── utils.did ├── dfx.json ├── jest.config.mjs ├── package.json ├── readme.MD ├── src └── evm_utils │ ├── Cargo.toml │ └── src │ ├── hash.rs │ ├── lib.rs │ ├── rlp.rs │ ├── transaction.rs │ ├── tree.rs │ ├── types │ ├── access_list.rs │ ├── address.rs │ ├── errors.rs │ ├── mod.rs │ ├── num.rs │ ├── rlp.rs │ ├── signature.rs │ ├── transaction.rs │ ├── transaction_1559.rs │ ├── transaction_2930.rs │ └── transaction_legacy.rs │ └── utils.rs └── yarn.lock /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [16.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - name: Wasm32 30 | run: rustup target add wasm32-unknown-unknown 31 | - run: npm i yarn 32 | - run: yarn 33 | - run: yarn build 34 | - run: yarn test 35 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 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 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Wasm32 20 | run: rustup target add wasm32-unknown-unknown 21 | - name: Build 22 | run: cargo build --verbose --target wasm32-unknown-unknown --release 23 | - name: Run tests 24 | run: cargo test --verbose 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Various IDEs and Editors 2 | .vscode/ 3 | .idea/ 4 | **/*~ 5 | 6 | # dfx temporary files 7 | .dfx/ 8 | 9 | # generated files 10 | src/declarations/ 11 | 12 | # rust 13 | target/ 14 | 15 | # node 16 | node_modules 17 | coverage -------------------------------------------------------------------------------- /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 = "ahash" 7 | version = "0.8.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" 10 | dependencies = [ 11 | "cfg-if", 12 | "once_cell", 13 | "version_check", 14 | ] 15 | 16 | [[package]] 17 | name = "aho-corasick" 18 | version = "0.7.20" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 21 | dependencies = [ 22 | "memchr", 23 | ] 24 | 25 | [[package]] 26 | name = "anyhow" 27 | version = "1.0.68" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" 30 | 31 | [[package]] 32 | name = "arrayvec" 33 | version = "0.5.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 36 | 37 | [[package]] 38 | name = "ascii-canvas" 39 | version = "3.0.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" 42 | dependencies = [ 43 | "term", 44 | ] 45 | 46 | [[package]] 47 | name = "atty" 48 | version = "0.2.14" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 51 | dependencies = [ 52 | "hermit-abi", 53 | "libc", 54 | "winapi", 55 | ] 56 | 57 | [[package]] 58 | name = "autocfg" 59 | version = "1.1.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 62 | 63 | [[package]] 64 | name = "beef" 65 | version = "0.5.2" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" 68 | 69 | [[package]] 70 | name = "binread" 71 | version = "2.2.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "16598dfc8e6578e9b597d9910ba2e73618385dc9f4b1d43dd92c349d6be6418f" 74 | dependencies = [ 75 | "binread_derive", 76 | "lazy_static", 77 | "rustversion", 78 | ] 79 | 80 | [[package]] 81 | name = "binread_derive" 82 | version = "2.1.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "1d9672209df1714ee804b1f4d4f68c8eb2a90b1f7a07acf472f88ce198ef1fed" 85 | dependencies = [ 86 | "either", 87 | "proc-macro2", 88 | "quote", 89 | "syn", 90 | ] 91 | 92 | [[package]] 93 | name = "bit-set" 94 | version = "0.5.3" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 97 | dependencies = [ 98 | "bit-vec", 99 | ] 100 | 101 | [[package]] 102 | name = "bit-vec" 103 | version = "0.6.3" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 106 | 107 | [[package]] 108 | name = "bitflags" 109 | version = "1.3.2" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 112 | 113 | [[package]] 114 | name = "block-buffer" 115 | version = "0.10.3" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 118 | dependencies = [ 119 | "generic-array", 120 | ] 121 | 122 | [[package]] 123 | name = "byteorder" 124 | version = "1.4.3" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 127 | 128 | [[package]] 129 | name = "bytes" 130 | version = "1.3.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" 133 | 134 | [[package]] 135 | name = "candid" 136 | version = "0.8.4" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "244005a1917bb7614cd775ca8a5d59efeb5ac74397bb14ba29a19347ebd78591" 139 | dependencies = [ 140 | "anyhow", 141 | "binread", 142 | "byteorder", 143 | "candid_derive", 144 | "codespan-reporting", 145 | "crc32fast", 146 | "data-encoding", 147 | "hex", 148 | "lalrpop", 149 | "lalrpop-util", 150 | "leb128", 151 | "logos", 152 | "num-bigint", 153 | "num-traits", 154 | "num_enum", 155 | "paste", 156 | "pretty", 157 | "serde", 158 | "serde_bytes", 159 | "sha2", 160 | "thiserror", 161 | ] 162 | 163 | [[package]] 164 | name = "candid_derive" 165 | version = "0.5.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "58f1f4db7c7d04b87b70b3a35c5dc5c2c9dd73cef8bdf6760e2f18a0d45350dd" 168 | dependencies = [ 169 | "lazy_static", 170 | "proc-macro2", 171 | "quote", 172 | "syn", 173 | ] 174 | 175 | [[package]] 176 | name = "cc" 177 | version = "1.0.78" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" 180 | 181 | [[package]] 182 | name = "cfg-if" 183 | version = "1.0.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 186 | 187 | [[package]] 188 | name = "cita_trie" 189 | version = "4.0.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "afe7baab47f510f52ca8dc9c0eb9082020c627c7f22285bea30edc3511f7ee29" 192 | dependencies = [ 193 | "hasher", 194 | "parking_lot", 195 | "rlp", 196 | ] 197 | 198 | [[package]] 199 | name = "codespan-reporting" 200 | version = "0.11.1" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 203 | dependencies = [ 204 | "termcolor", 205 | "unicode-width", 206 | ] 207 | 208 | [[package]] 209 | name = "cpufeatures" 210 | version = "0.2.5" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 213 | dependencies = [ 214 | "libc", 215 | ] 216 | 217 | [[package]] 218 | name = "crc32fast" 219 | version = "1.3.2" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 222 | dependencies = [ 223 | "cfg-if", 224 | ] 225 | 226 | [[package]] 227 | name = "crunchy" 228 | version = "0.2.2" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 231 | 232 | [[package]] 233 | name = "crypto-common" 234 | version = "0.1.6" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 237 | dependencies = [ 238 | "generic-array", 239 | "typenum", 240 | ] 241 | 242 | [[package]] 243 | name = "data-encoding" 244 | version = "2.3.3" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" 247 | 248 | [[package]] 249 | name = "diff" 250 | version = "0.1.13" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 253 | 254 | [[package]] 255 | name = "digest" 256 | version = "0.10.6" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" 259 | dependencies = [ 260 | "block-buffer", 261 | "crypto-common", 262 | ] 263 | 264 | [[package]] 265 | name = "dirs-next" 266 | version = "2.0.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 269 | dependencies = [ 270 | "cfg-if", 271 | "dirs-sys-next", 272 | ] 273 | 274 | [[package]] 275 | name = "dirs-sys-next" 276 | version = "0.1.2" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 279 | dependencies = [ 280 | "libc", 281 | "redox_users", 282 | "winapi", 283 | ] 284 | 285 | [[package]] 286 | name = "either" 287 | version = "1.8.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 290 | 291 | [[package]] 292 | name = "ena" 293 | version = "0.14.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" 296 | dependencies = [ 297 | "log", 298 | ] 299 | 300 | [[package]] 301 | name = "evm_utils" 302 | version = "0.1.0" 303 | dependencies = [ 304 | "bytes", 305 | "candid", 306 | "cita_trie", 307 | "hasher", 308 | "hex", 309 | "ic-cdk", 310 | "ic-cdk-macros", 311 | "rlp", 312 | "secp256k1", 313 | "serde", 314 | "sha3", 315 | "trie-db", 316 | ] 317 | 318 | [[package]] 319 | name = "fixedbitset" 320 | version = "0.4.2" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 323 | 324 | [[package]] 325 | name = "fnv" 326 | version = "1.0.7" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 329 | 330 | [[package]] 331 | name = "generic-array" 332 | version = "0.14.6" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 335 | dependencies = [ 336 | "typenum", 337 | "version_check", 338 | ] 339 | 340 | [[package]] 341 | name = "getrandom" 342 | version = "0.2.8" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 345 | dependencies = [ 346 | "cfg-if", 347 | "libc", 348 | "wasi", 349 | ] 350 | 351 | [[package]] 352 | name = "hash-db" 353 | version = "0.15.2" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" 356 | 357 | [[package]] 358 | name = "hashbrown" 359 | version = "0.12.3" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 362 | 363 | [[package]] 364 | name = "hashbrown" 365 | version = "0.13.2" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" 368 | dependencies = [ 369 | "ahash", 370 | ] 371 | 372 | [[package]] 373 | name = "hasher" 374 | version = "0.1.4" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "bbbba678b6567f27ce22870d951f4208e1dc2fef64993bd4521b1d497ef8a3aa" 377 | dependencies = [ 378 | "tiny-keccak", 379 | ] 380 | 381 | [[package]] 382 | name = "hermit-abi" 383 | version = "0.1.19" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 386 | dependencies = [ 387 | "libc", 388 | ] 389 | 390 | [[package]] 391 | name = "hex" 392 | version = "0.4.3" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 395 | 396 | [[package]] 397 | name = "ic-cdk" 398 | version = "0.6.10" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "c98b304a2657bad15bcb547625a018e13cf596676d834cfd93023395a6e2e03a" 401 | dependencies = [ 402 | "candid", 403 | "cfg-if", 404 | "ic-cdk-macros", 405 | "ic0", 406 | "serde", 407 | "serde_bytes", 408 | ] 409 | 410 | [[package]] 411 | name = "ic-cdk-macros" 412 | version = "0.6.8" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "cb423dab7c5bf19d4abccabd2ffe35e09b9dde611d478ea3afe0347b50fa727f" 415 | dependencies = [ 416 | "candid", 417 | "proc-macro2", 418 | "quote", 419 | "serde", 420 | "serde_tokenstream", 421 | "syn", 422 | ] 423 | 424 | [[package]] 425 | name = "ic0" 426 | version = "0.18.9" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "978b91fc78de9d2eb0144db717839cde3b35470199ea51aca362cb6310e93dfd" 429 | 430 | [[package]] 431 | name = "indexmap" 432 | version = "1.9.2" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" 435 | dependencies = [ 436 | "autocfg", 437 | "hashbrown 0.12.3", 438 | ] 439 | 440 | [[package]] 441 | name = "itertools" 442 | version = "0.10.5" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 445 | dependencies = [ 446 | "either", 447 | ] 448 | 449 | [[package]] 450 | name = "keccak" 451 | version = "0.1.3" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" 454 | dependencies = [ 455 | "cpufeatures", 456 | ] 457 | 458 | [[package]] 459 | name = "lalrpop" 460 | version = "0.19.8" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "b30455341b0e18f276fa64540aff54deafb54c589de6aca68659c63dd2d5d823" 463 | dependencies = [ 464 | "ascii-canvas", 465 | "atty", 466 | "bit-set", 467 | "diff", 468 | "ena", 469 | "itertools", 470 | "lalrpop-util", 471 | "petgraph", 472 | "pico-args", 473 | "regex", 474 | "regex-syntax", 475 | "string_cache", 476 | "term", 477 | "tiny-keccak", 478 | "unicode-xid", 479 | ] 480 | 481 | [[package]] 482 | name = "lalrpop-util" 483 | version = "0.19.8" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "bcf796c978e9b4d983414f4caedc9273aa33ee214c5b887bd55fde84c85d2dc4" 486 | dependencies = [ 487 | "regex", 488 | ] 489 | 490 | [[package]] 491 | name = "lazy_static" 492 | version = "1.4.0" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 495 | 496 | [[package]] 497 | name = "leb128" 498 | version = "0.2.5" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 501 | 502 | [[package]] 503 | name = "libc" 504 | version = "0.2.139" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 507 | 508 | [[package]] 509 | name = "lock_api" 510 | version = "0.4.9" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 513 | dependencies = [ 514 | "autocfg", 515 | "scopeguard", 516 | ] 517 | 518 | [[package]] 519 | name = "log" 520 | version = "0.4.17" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 523 | dependencies = [ 524 | "cfg-if", 525 | ] 526 | 527 | [[package]] 528 | name = "logos" 529 | version = "0.12.1" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" 532 | dependencies = [ 533 | "logos-derive", 534 | ] 535 | 536 | [[package]] 537 | name = "logos-derive" 538 | version = "0.12.1" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" 541 | dependencies = [ 542 | "beef", 543 | "fnv", 544 | "proc-macro2", 545 | "quote", 546 | "regex-syntax", 547 | "syn", 548 | ] 549 | 550 | [[package]] 551 | name = "memchr" 552 | version = "2.5.0" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 555 | 556 | [[package]] 557 | name = "new_debug_unreachable" 558 | version = "1.0.4" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 561 | 562 | [[package]] 563 | name = "num-bigint" 564 | version = "0.4.3" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" 567 | dependencies = [ 568 | "autocfg", 569 | "num-integer", 570 | "num-traits", 571 | "serde", 572 | ] 573 | 574 | [[package]] 575 | name = "num-integer" 576 | version = "0.1.45" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 579 | dependencies = [ 580 | "autocfg", 581 | "num-traits", 582 | ] 583 | 584 | [[package]] 585 | name = "num-traits" 586 | version = "0.2.15" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 589 | dependencies = [ 590 | "autocfg", 591 | ] 592 | 593 | [[package]] 594 | name = "num_enum" 595 | version = "0.5.7" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" 598 | dependencies = [ 599 | "num_enum_derive", 600 | ] 601 | 602 | [[package]] 603 | name = "num_enum_derive" 604 | version = "0.5.7" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" 607 | dependencies = [ 608 | "proc-macro-crate", 609 | "proc-macro2", 610 | "quote", 611 | "syn", 612 | ] 613 | 614 | [[package]] 615 | name = "once_cell" 616 | version = "1.17.0" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 619 | 620 | [[package]] 621 | name = "parking_lot" 622 | version = "0.12.1" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 625 | dependencies = [ 626 | "lock_api", 627 | "parking_lot_core", 628 | ] 629 | 630 | [[package]] 631 | name = "parking_lot_core" 632 | version = "0.9.6" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" 635 | dependencies = [ 636 | "cfg-if", 637 | "libc", 638 | "redox_syscall", 639 | "smallvec", 640 | "windows-sys", 641 | ] 642 | 643 | [[package]] 644 | name = "paste" 645 | version = "1.0.11" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" 648 | 649 | [[package]] 650 | name = "petgraph" 651 | version = "0.6.2" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" 654 | dependencies = [ 655 | "fixedbitset", 656 | "indexmap", 657 | ] 658 | 659 | [[package]] 660 | name = "phf_shared" 661 | version = "0.10.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 664 | dependencies = [ 665 | "siphasher", 666 | ] 667 | 668 | [[package]] 669 | name = "pico-args" 670 | version = "0.4.2" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" 673 | 674 | [[package]] 675 | name = "precomputed-hash" 676 | version = "0.1.1" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 679 | 680 | [[package]] 681 | name = "pretty" 682 | version = "0.10.0" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "ad9940b913ee56ddd94aec2d3cd179dd47068236f42a1a6415ccf9d880ce2a61" 685 | dependencies = [ 686 | "arrayvec", 687 | "typed-arena", 688 | ] 689 | 690 | [[package]] 691 | name = "proc-macro-crate" 692 | version = "1.2.1" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" 695 | dependencies = [ 696 | "once_cell", 697 | "thiserror", 698 | "toml", 699 | ] 700 | 701 | [[package]] 702 | name = "proc-macro2" 703 | version = "1.0.50" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" 706 | dependencies = [ 707 | "unicode-ident", 708 | ] 709 | 710 | [[package]] 711 | name = "quote" 712 | version = "1.0.23" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 715 | dependencies = [ 716 | "proc-macro2", 717 | ] 718 | 719 | [[package]] 720 | name = "redox_syscall" 721 | version = "0.2.16" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 724 | dependencies = [ 725 | "bitflags", 726 | ] 727 | 728 | [[package]] 729 | name = "redox_users" 730 | version = "0.4.3" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 733 | dependencies = [ 734 | "getrandom", 735 | "redox_syscall", 736 | "thiserror", 737 | ] 738 | 739 | [[package]] 740 | name = "regex" 741 | version = "1.7.1" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" 744 | dependencies = [ 745 | "aho-corasick", 746 | "memchr", 747 | "regex-syntax", 748 | ] 749 | 750 | [[package]] 751 | name = "regex-syntax" 752 | version = "0.6.28" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 755 | 756 | [[package]] 757 | name = "rlp" 758 | version = "0.5.2" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" 761 | dependencies = [ 762 | "bytes", 763 | "rustc-hex", 764 | ] 765 | 766 | [[package]] 767 | name = "rustc-hex" 768 | version = "2.1.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" 771 | 772 | [[package]] 773 | name = "rustversion" 774 | version = "1.0.11" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" 777 | 778 | [[package]] 779 | name = "scopeguard" 780 | version = "1.1.0" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 783 | 784 | [[package]] 785 | name = "secp256k1" 786 | version = "0.26.0" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "4124a35fe33ae14259c490fd70fa199a32b9ce9502f2ee6bc4f81ec06fa65894" 789 | dependencies = [ 790 | "secp256k1-sys", 791 | ] 792 | 793 | [[package]] 794 | name = "secp256k1-sys" 795 | version = "0.8.0" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "642a62736682fdd8c71da0eb273e453c8ac74e33b9fb310e22ba5b03ec7651ff" 798 | dependencies = [ 799 | "cc", 800 | ] 801 | 802 | [[package]] 803 | name = "serde" 804 | version = "1.0.152" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 807 | dependencies = [ 808 | "serde_derive", 809 | ] 810 | 811 | [[package]] 812 | name = "serde_bytes" 813 | version = "0.11.8" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "718dc5fff5b36f99093fc49b280cfc96ce6fc824317783bff5a1fed0c7a64819" 816 | dependencies = [ 817 | "serde", 818 | ] 819 | 820 | [[package]] 821 | name = "serde_derive" 822 | version = "1.0.152" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" 825 | dependencies = [ 826 | "proc-macro2", 827 | "quote", 828 | "syn", 829 | ] 830 | 831 | [[package]] 832 | name = "serde_tokenstream" 833 | version = "0.1.6" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "274f512d6748a01e67cbcde5b4307ab2c9d52a98a2b870a980ef0793a351deff" 836 | dependencies = [ 837 | "proc-macro2", 838 | "serde", 839 | "syn", 840 | ] 841 | 842 | [[package]] 843 | name = "sha2" 844 | version = "0.10.6" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" 847 | dependencies = [ 848 | "cfg-if", 849 | "cpufeatures", 850 | "digest", 851 | ] 852 | 853 | [[package]] 854 | name = "sha3" 855 | version = "0.10.6" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" 858 | dependencies = [ 859 | "digest", 860 | "keccak", 861 | ] 862 | 863 | [[package]] 864 | name = "siphasher" 865 | version = "0.3.10" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" 868 | 869 | [[package]] 870 | name = "smallvec" 871 | version = "1.10.0" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 874 | 875 | [[package]] 876 | name = "string_cache" 877 | version = "0.8.4" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" 880 | dependencies = [ 881 | "new_debug_unreachable", 882 | "once_cell", 883 | "parking_lot", 884 | "phf_shared", 885 | "precomputed-hash", 886 | ] 887 | 888 | [[package]] 889 | name = "syn" 890 | version = "1.0.107" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 893 | dependencies = [ 894 | "proc-macro2", 895 | "quote", 896 | "unicode-ident", 897 | ] 898 | 899 | [[package]] 900 | name = "term" 901 | version = "0.7.0" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 904 | dependencies = [ 905 | "dirs-next", 906 | "rustversion", 907 | "winapi", 908 | ] 909 | 910 | [[package]] 911 | name = "termcolor" 912 | version = "1.2.0" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 915 | dependencies = [ 916 | "winapi-util", 917 | ] 918 | 919 | [[package]] 920 | name = "thiserror" 921 | version = "1.0.38" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" 924 | dependencies = [ 925 | "thiserror-impl", 926 | ] 927 | 928 | [[package]] 929 | name = "thiserror-impl" 930 | version = "1.0.38" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" 933 | dependencies = [ 934 | "proc-macro2", 935 | "quote", 936 | "syn", 937 | ] 938 | 939 | [[package]] 940 | name = "tiny-keccak" 941 | version = "2.0.2" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 944 | dependencies = [ 945 | "crunchy", 946 | ] 947 | 948 | [[package]] 949 | name = "toml" 950 | version = "0.5.11" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 953 | dependencies = [ 954 | "serde", 955 | ] 956 | 957 | [[package]] 958 | name = "trie-db" 959 | version = "0.25.0" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "9bcd4e7fbb3bcab17b02596b53876e36eb39663ddd87884bfd88c69cdeb2ebd6" 962 | dependencies = [ 963 | "hash-db", 964 | "hashbrown 0.13.2", 965 | "log", 966 | "rustc-hex", 967 | "smallvec", 968 | ] 969 | 970 | [[package]] 971 | name = "typed-arena" 972 | version = "2.0.2" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" 975 | 976 | [[package]] 977 | name = "typenum" 978 | version = "1.16.0" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 981 | 982 | [[package]] 983 | name = "unicode-ident" 984 | version = "1.0.6" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 987 | 988 | [[package]] 989 | name = "unicode-width" 990 | version = "0.1.10" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 993 | 994 | [[package]] 995 | name = "unicode-xid" 996 | version = "0.2.4" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 999 | 1000 | [[package]] 1001 | name = "version_check" 1002 | version = "0.9.4" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1005 | 1006 | [[package]] 1007 | name = "wasi" 1008 | version = "0.11.0+wasi-snapshot-preview1" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1011 | 1012 | [[package]] 1013 | name = "winapi" 1014 | version = "0.3.9" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1017 | dependencies = [ 1018 | "winapi-i686-pc-windows-gnu", 1019 | "winapi-x86_64-pc-windows-gnu", 1020 | ] 1021 | 1022 | [[package]] 1023 | name = "winapi-i686-pc-windows-gnu" 1024 | version = "0.4.0" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1027 | 1028 | [[package]] 1029 | name = "winapi-util" 1030 | version = "0.1.5" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1033 | dependencies = [ 1034 | "winapi", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "winapi-x86_64-pc-windows-gnu" 1039 | version = "0.4.0" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1042 | 1043 | [[package]] 1044 | name = "windows-sys" 1045 | version = "0.42.0" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 1048 | dependencies = [ 1049 | "windows_aarch64_gnullvm", 1050 | "windows_aarch64_msvc", 1051 | "windows_i686_gnu", 1052 | "windows_i686_msvc", 1053 | "windows_x86_64_gnu", 1054 | "windows_x86_64_gnullvm", 1055 | "windows_x86_64_msvc", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "windows_aarch64_gnullvm" 1060 | version = "0.42.1" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 1063 | 1064 | [[package]] 1065 | name = "windows_aarch64_msvc" 1066 | version = "0.42.1" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 1069 | 1070 | [[package]] 1071 | name = "windows_i686_gnu" 1072 | version = "0.42.1" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 1075 | 1076 | [[package]] 1077 | name = "windows_i686_msvc" 1078 | version = "0.42.1" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 1081 | 1082 | [[package]] 1083 | name = "windows_x86_64_gnu" 1084 | version = "0.42.1" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 1087 | 1088 | [[package]] 1089 | name = "windows_x86_64_gnullvm" 1090 | version = "0.42.1" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 1093 | 1094 | [[package]] 1095 | name = "windows_x86_64_msvc" 1096 | version = "0.42.1" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 1099 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "src/evm_utils", 4 | ] 5 | 6 | [profile.release] 7 | opt-level="s" 8 | lto = true 9 | codegen-units = 1 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ethan Celletti 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. -------------------------------------------------------------------------------- /__tests__/_common.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import fetch, { Headers } from 'node-fetch' 3 | 4 | import { TestContext } from 'lightic'; 5 | import { Principal } from '@dfinity/principal'; 6 | import { HttpAgent, Actor } from '@dfinity/agent'; 7 | import { idlFactory } from './evm_utils.did'; 8 | 9 | let context = undefined; 10 | 11 | if (!globalThis.fetch) { 12 | globalThis.fetch = fetch 13 | globalThis.Headers = Headers 14 | } 15 | 16 | export async function initCanisters() { 17 | if (context !== undefined) return; 18 | context = new TestContext(); 19 | return await context.deploy("target/wasm32-unknown-unknown/release/evm_utils.wasm") 20 | } 21 | 22 | //Returns actor for token canister 23 | export async function getActor() { 24 | if (process.env.USE_DFX !== undefined) { 25 | let agent = new HttpAgent({ 26 | host: 'http://127.0.0.1:4943' 27 | }) 28 | await agent.fetchRootKey() 29 | 30 | let actor = Actor.createActor(idlFactory, { 31 | agent: agent, 32 | canisterId: 'rrkah-fqaaa-aaaaa-aaaaq-cai', 33 | }); 34 | 35 | return actor; 36 | } else { 37 | let canister = await initCanisters(); 38 | let actor = context.getAgent(Principal.anonymous()).getActor(canister); 39 | 40 | return actor; 41 | } 42 | } -------------------------------------------------------------------------------- /__tests__/evm_utils.did.js: -------------------------------------------------------------------------------- 1 | export const idlFactory = ({ IDL }) => { 2 | const List = IDL.Rec(); 3 | const Signature = IDL.Record({ 4 | 'r' : IDL.Vec(IDL.Nat8), 5 | 's' : IDL.Vec(IDL.Nat8), 6 | 'v' : IDL.Nat64, 7 | 'from' : IDL.Opt(IDL.Vec(IDL.Nat8)), 8 | 'hash' : IDL.Vec(IDL.Nat8), 9 | }); 10 | const AccessList = IDL.Record({ 11 | 'storage_keys' : IDL.Vec(IDL.Vec(IDL.Nat8)), 12 | 'address' : IDL.Vec(IDL.Nat8), 13 | }); 14 | const Transaction1559 = IDL.Record({ 15 | 'to' : IDL.Vec(IDL.Nat8), 16 | 'value' : IDL.Vec(IDL.Nat8), 17 | 'max_priority_fee_per_gas' : IDL.Vec(IDL.Nat8), 18 | 'data' : IDL.Vec(IDL.Nat8), 19 | 'sign' : IDL.Opt(Signature), 20 | 'max_fee_per_gas' : IDL.Vec(IDL.Nat8), 21 | 'chain_id' : IDL.Nat64, 22 | 'nonce' : IDL.Vec(IDL.Nat8), 23 | 'gas_limit' : IDL.Vec(IDL.Nat8), 24 | 'access_list' : IDL.Vec(AccessList), 25 | }); 26 | const Transaction2930 = IDL.Record({ 27 | 'to' : IDL.Vec(IDL.Nat8), 28 | 'value' : IDL.Vec(IDL.Nat8), 29 | 'data' : IDL.Vec(IDL.Nat8), 30 | 'sign' : IDL.Opt(Signature), 31 | 'chain_id' : IDL.Nat64, 32 | 'nonce' : IDL.Vec(IDL.Nat8), 33 | 'gas_limit' : IDL.Vec(IDL.Nat8), 34 | 'access_list' : IDL.Vec(AccessList), 35 | 'gas_price' : IDL.Vec(IDL.Nat8), 36 | }); 37 | const TransactionLegacy = IDL.Record({ 38 | 'to' : IDL.Vec(IDL.Nat8), 39 | 'value' : IDL.Vec(IDL.Nat8), 40 | 'data' : IDL.Vec(IDL.Nat8), 41 | 'sign' : IDL.Opt(Signature), 42 | 'chain_id' : IDL.Nat64, 43 | 'nonce' : IDL.Vec(IDL.Nat8), 44 | 'gas_limit' : IDL.Vec(IDL.Nat8), 45 | 'gas_price' : IDL.Vec(IDL.Nat8), 46 | }); 47 | const Transaction = IDL.Variant({ 48 | 'EIP1559' : Transaction1559, 49 | 'EIP2930' : Transaction2930, 50 | 'Legacy' : TransactionLegacy, 51 | }); 52 | const Result = IDL.Variant({ 53 | 'Ok' : IDL.Tuple(IDL.Vec(IDL.Nat8), IDL.Vec(IDL.Nat8)), 54 | 'Err' : IDL.Text, 55 | }); 56 | const Result_1 = IDL.Variant({ 'Ok' : IDL.Null, 'Err' : IDL.Text }); 57 | const Result_2 = IDL.Variant({ 'Ok' : Transaction, 'Err' : IDL.Text }); 58 | const Result_3 = IDL.Variant({ 'Ok' : IDL.Vec(IDL.Nat8), 'Err' : IDL.Text }); 59 | const Item = IDL.Variant({ 60 | 'Num' : IDL.Nat64, 61 | 'Raw' : IDL.Vec(IDL.Nat8), 62 | 'Empty' : IDL.Null, 63 | 'List' : List, 64 | 'Text' : IDL.Text, 65 | }); 66 | List.fill(IDL.Record({ 'values' : IDL.Vec(Item) })); 67 | const Result_4 = IDL.Variant({ 'Ok' : List, 'Err' : IDL.Text }); 68 | const Result_5 = IDL.Variant({ 69 | 'Ok' : IDL.Opt(IDL.Vec(IDL.Nat8)), 70 | 'Err' : IDL.Text, 71 | }); 72 | return IDL.Service({ 73 | 'create_transaction' : IDL.Func([Transaction], [Result], ['query']), 74 | 'encode_signed_transaction' : IDL.Func([Transaction], [Result], ['query']), 75 | 'is_valid_public' : IDL.Func([IDL.Vec(IDL.Nat8)], [Result_1], ['query']), 76 | 'is_valid_signature' : IDL.Func([IDL.Vec(IDL.Nat8)], [Result_1], ['query']), 77 | 'keccak256' : IDL.Func([IDL.Vec(IDL.Nat8)], [IDL.Vec(IDL.Nat8)], ['query']), 78 | 'parse_transaction' : IDL.Func([IDL.Vec(IDL.Nat8)], [Result_2], ['query']), 79 | 'pub_to_address' : IDL.Func([IDL.Vec(IDL.Nat8)], [Result_3], ['query']), 80 | 'recover_public_key' : IDL.Func( 81 | [IDL.Vec(IDL.Nat8), IDL.Vec(IDL.Nat8)], 82 | [Result_3], 83 | ['query'], 84 | ), 85 | 'rlp_decode' : IDL.Func([IDL.Vec(IDL.Nat8)], [Result_4], ['query']), 86 | 'rlp_encode' : IDL.Func([List], [Result_3], ['query']), 87 | 'verify_proof' : IDL.Func( 88 | [IDL.Vec(IDL.Nat8), IDL.Vec(IDL.Nat8), IDL.Vec(IDL.Vec(IDL.Nat8))], 89 | [Result_5], 90 | ['query'], 91 | ), 92 | }); 93 | }; 94 | export const init = ({ IDL }) => { return []; }; 95 | -------------------------------------------------------------------------------- /__tests__/proof.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "result": { 4 | "address": "0xcca577ee56d30a444c73f8fc8d5ce34ed1c7da8b", 5 | "accountProof": [ 6 | "0xf90211a00dd762bf36a15c237b9c0fe5943b83a62cec73c9ee1f22f7dc0c4b1a349cd82ba0709ed2c0bdf63a76a9260ad59de8a886a331aead9f4ab3f09fb735b6fb6303cfa060d62a951c7ddb90ce2ec7be3abca401c455ee0efc10a9c250cbf022e9bf6f94a011bd451b5ed730318573231431a68e4e50977b52a0ff8ac206a8f6848ec2d072a0bb28f119d3b0c75a64cad6310bfda39c7db06807c803b471f5cf6b74a11a2beba017818af848bceb2d3541c424659fded5dd87d81123d47f43a38fb59a5c829dffa0aafc524103348d389d9408ba488e1a08d7a12644261d7228062517a25ff8c7f5a0a236cfe006437a61fc44b3ba4c0fc040d8c0714c346b06e38614e463b63cabb7a037a7afa33133820091d88a6f66ad76158b6d4895fa907b36e0adb676b1d99bb7a05d16ad5425b869f2b6765d5447890cd490b181c3d216d0bc3606b9a52eaa6e04a00cbba5516a1341967d40780b6847c6c783861659c3f54c3bad2945dc6009706ba0b47282adf50fe9a0cad9b2e203f8ea8cc8fb88e877fa40c226f007107ac868aea0ff1fa9c959359ad2e209ff0d870c000aecee281b9bdd975b64a094c77885361da0e3fd47d85d0a85870f95eb281ac085236534a814b09f6fbb317a87a63ead7caaa02dcb46674b4c02427539c4d559251993dc8d00c25d4d0ba18b66ee246afa39ffa04a053fe407f9c77e9da7bde422cfa64e87806e5fe252e8826535f47aecd14aad80", 7 | "0xf90211a0564c5a64c0f0872ce5d72c48465a5a541f0c9d6e6c372479d217be06b576f60ea040bf1000da0d6f81b50b6a50d56fa2489ceeedabe4c5cd9879a3923e84db2043a0df15ad479e74dcd8bf5d024ade0a412c9f8a36c5eadbd4ebc94dfdefabb89b09a0e14109764c91d2b49bf4f8e87fb53dacfa727204bc64c988e89131da7f2c2049a0983348c4c02559265c6a88e16c225f97f0af029253c7c57373d91f4f65bfe4b4a0b900c6c38d4f10fa5d312102fe206022aef860370818f843dbecec19a78178f2a0c36c545f6f3f5f59a4806fe2d2d287112ea4a7ed9ae9afd29e4b73eb5b0621cba02f58a3dea2cd2a49f30d1da9cda4910fbade64ec421c64ea0603817e38edc449a08aa1f1f1b40785a5057a665b4270faed8c81355832daa77f161d13d8f6d202d9a0b6615716b8a5e65ba772df1695524d1b027caeaf58c0f3f36134bec8bf364640a0dabdfc6a5def49149c5f5795f42b1906363ee5253c637efa164ffe7bae0a0b22a0d738ec4722774314014c0b7265684027fafdee4476bc9510676904669aeb4d93a02fc5317ae5506ecf490940fa55517dbf4454d9d9c97d8834052041f3af50cd09a0a58598743b5eb6ebbfebc10dc4263a8312d821c1f2374621de4d7721ac2e89cea03d6f76a0f80e165f4e75fc1bcfba614bb0fab5e66354a89b39f0cd99e3ecd5a5a0d32f0ab8963a9e042a2d8fea286feb7d6ed8afab704d802a98d8b0d3b9e79f2980", 8 | "0xf90211a0aff572e77737abd04ae87b1259d26b8a319f8e0eb9ce9dcc46451cb01b3b3350a0572913c1d0e0e8d0b798908e358cbcc8814bdb76ccbc46273e10cca576c58052a0b1992a096ba36723d54e03c58f427054650f554b34aceeb5da556f659a73f1cca07f34c5d5a9ab6223429dc6f1baf28686d02e7d4f15baeb01de70fa35e404936ca02600fc07b02e17f358115a559b14eae0b2b3ff04a6c1ea3d4be312260bc131daa079bef767a6f14428d385b3ea30d42d68d6efc1fcd6fcd684a1d933c76d9f32fca076acf329236d48dbf8606256d86b08e967cc406db6bc30ee03ceecebe6a1381da0f39488a90099c16206b87f77234673cfe1c55338e859998a8801df036728d1bba0f6cfa50dc062af5453918002355ff2fb244561511fdb70f0a15819670b0749dca062f0220be7d33c69b229b4af5b5f5dafa7d6c68611205ad7544306b50db9868fa08cfa39e62a3781465411ade68cb9ae705e06b28fa3ea2e7d08ca8f63b4d38194a05ae3f595ed49c44d4f2701e0a0c9239a83d081fda4c10e426cb1f024c457553aa01c95e232beba4545d225eecd482d1b06d73b3589de37fa6bb74bc191c9283118a026a7f6445cc4929371c08826f930abe8dbdb7a58c24b6cfe205f5a4c4aed39b5a0fcc15967dcb24ab54e79905fdb6bfd33a3c1dc56e389a7c91c57074216c30e63a006da2488e98d0ed056ca9f546150574078dc962860fcb613364e4fd50289d59c80", 9 | "0xf90211a048772ce5886144f79e98ab0a2c9a0812447ca50b90108b8b89aef870be9c2d2ca0ad8ec52121dc71272d85e0cfb0cb2ef60c661daf052b325d5582680171ecadf9a050ffe79b7d56df10248e110c17a25578e99c17410935faffa0d89a22283e4e6da0582566fc761f38708c3ccd7aabb600a4478a6d93fd2656be1d947ad47002d6fea086bc7d35b4837393f975325e6adda16af17f81bf480bfff989592a4db85f9275a0721cf2ea42d441b3dfe2217bec592d9a01b1aea652be5f2c7c4c3889589ec5f0a0d4a9af2bbb3e6daa1860deac8d07562cfff350400555bcb49323eac89409b64da063faf3c7091b72cf4e6cf40673475c775644feb11ef765df2f57790b135e8c66a0b6ec5e3960f3baec5f522bc8733faf222cc5e5427f26e2cbc098620a8d31f1b2a071e7a173399fbceb1021813c4c89a8337ba49448d96cf9d0607b4b2d8b7cd12ea0819718965a20770ec11dbce1569c93e88dfaab4fd8179cc67eb0bec3681bf0baa0e7d256f382961a9dcb1e27dec656305af00dc7cfddb390fc7d3b91835f58a70da0d52a32be23c4457ffc6b42b5aec154de88bb96dd2273f52487ab8588c7b4e29da0118818e161e768c20846ff38650436a566b349b8b5460a30bd5a6df1d0db041da0576ba13c45de038fd2f0b9a03219f8c003479e577bae940c4e871ad766460afca0cc71ff5ead49c816cd1419e72b2608a97968b14acfdefe81cf403f6a81afe3e580", 10 | "0xf90211a028ac28dfef9ee2157d60bc7d1116fcf6f7a2b3817ae64ccdac85a3492bff6d85a04a51eb58047d0711b6ac374c97d3ac30279e297c27183ce4e63c2c35f44bb1f8a0964daacb8a177d46b21c21636cc4d4165c4459d3377f3928a72e7c611db4e7aea00c7fde9c30c8864bef29086e0d21b128a0cd79d9280a721863a5c0c2949163d9a0f41ae9855a2bad346dbc4b9db351e41b9635d9aa766d897bc0763a6b9fdfd52ba05857b3ca3e9069256dd6f87a5e09bab60697c30a50732a087bd2c1349f4cd0cba080d113ce969e09788b59eb6bccdd1b1708236e0e3a1149d4607bb0b2d279cb40a07b29e9ae0da45345b78f1d77deafa46418ba7eff6352c2c461dba88a7f1ba647a057fbd0ae2e5cc0b81fe155c863f7f77a0ded68c07f8655ddb5a4eaf7b3cc2afea0de4db535b85fc2983287ecfd313638bad8fedbd6905013c9918a4393485cc397a0bd351c1ffba42a12b48025dc7e209c04e1f9602d5172bdb5f95d71acb2481368a095236c614aa6fd6a7e6bf78761cb707d2bb731e82e9487217bfaa84eabd1cc6ba0238806868b99970961f672e2bcfb5eca053e0bb9200142f35aca94dd77f7253aa03d81a820ef3c18b6d29999fa79db804fa36947175525a53d91101fb865adfdfba05fb199fdeea50f748ec7ccd8425f23dab3d76f2422e8bebc06c135761df66d1da0893c19ebbf7dcb8a40e51763915854afe92b1226cbfcaea8c3156d28993ae3b680", 11 | "0xf90211a048a6917aa05d311cb271ff0cdb5708f6ca4f11c2abfeb8651fbe8f7144df6afba00ff5b1376f1bd4bf9ad9fc158573f3aa1f54cb94b843d20487ad538369b5f62da05fe8c362133cf0e1e034878e32f42764cbab041c42556a856bbc3700fc0d9ee4a03bdc76159e826d28a073b46cd4d8a33879d12da96ac4d727ca4a3bc0112ff75aa0ac597e57f0451b83a961dcafa6e4933315ad5939994f2a38c85741e416909b05a08070f31576be3fa0dab1491b35b042c9a49c79a2f32988ad33e2bcfe05c4cba8a062bcdf215bf3f082365908edbbaaf3ec4ad9cd30988bb7337aa145f90202605ca0aac2b6547f129955882acd9fb18239c7f92d543bd4fac87ebc127b1b580d4efaa06c5b3eecc80ff7eedcd72e1d7f831b229f4ea1ad9d145f4fb570180cd335fb95a0f1cd7e6302bf0dd20f0d13fd31b5c61cac5151fc5dedfc365d5f0220df5c96bca055c0a593a171ea447808ad252bf94907b16f7a456f4bac650ccc16cf92d98da2a0bfc16a97e0f77361437806ed7a2f9c5932a16c5f4f9b2a79591892a20b857abea0b562979690248b5f855aeb468930351a197165f64d10f2e98081514661847b6aa035b07e16e08a410f1064e0fb80fdf85e9885ceb9cf1ee678caaceebe458b1a03a0aec5d68abdff1143ea2435c0c8dd9a4ff551b5e6cc0ec512e9c59b59b9b017e0a0494e7e9695268ef122ed5bca4e18190f02adce70952b1bd4b0ab2e34f0f2a4cf80", 12 | "0xf90131a0ca660fe0511f3ffb9394bfae88d8381aab9ca4a3322b9b8d16c393dc88e61e6f80a047f02c461780201135898bc49a7225aa7a7c859d9bdfe28971a3f623b7500e1d808080a032223b190fb5091b7ceab5434b3baf7d45fd8802bb670a7630843fe2a6e932f780a0a01ec92dc038dd0ebd4f526e1180b9422e3a089283b94b03312a73575b71486280a0c2f294c89036c7958e643905a0a72cf72cf258b6db8260fc20d9775b73e2814fa0aa389e1f42c125603de2a1736e6ccb73f17707fb26f3f52aaef37438d5e1fee1a0c0d9ec2fed8f23bc30fa7ba454a57c5c6ecd71d9ffc10a11a4a89fd167eb2d5ea0d1ead5297994fefae95d2f51788fef9789c7b455a0a32f265641a1a83d8e23e2a0e28b21a22e6ec029761a9f74461d01334d31c9ca7e8f63fb59c190a9d530e62d8080", 13 | "0xe583009b0aa0f75c51e5fca85fd1d9a8aafab366d0b94de28ae6765785cf0733903a23e42ba7", 14 | "0xf851808080808080a05be5cea9db1f66179cb1ceab79e160c2342f03389e54a31a09f6d321742a083580808080a05d4aedb3cf18418d18eb54e998bcc37f77b353321cd33e69eaf931e17e1490aa8080808080", 15 | "0xf8649b209636eb04ea89f1fb5c295e61755c4ab1eb04df1bd5e963107aaab846f8448080a07317ebbe7d6c43dd6944ed0e2c5f79762113cb75fa0bed7124377c0814737fb4a06c7686cf8f47f081f63d3f53a87c3c2ca0de63eb057e795f5c0414c6f861f9e3" 16 | ], 17 | "balance": "0x0", 18 | "codeHash": "0x6c7686cf8f47f081f63d3f53a87c3c2ca0de63eb057e795f5c0414c6f861f9e3", 19 | "nonce": "0x0", 20 | "storageHash": "0x7317ebbe7d6c43dd6944ed0e2c5f79762113cb75fa0bed7124377c0814737fb4", 21 | "storageProof": [ 22 | { 23 | "key": "0x0", 24 | "value": "0xde74da73d5102a796559933296c73e7d1c6f37fb", 25 | "proof": [ 26 | "0xf8518080a0cd2a98a2ebb71b70e1109bf206dbc96dc73c76569b42df09ff269ecdcd31b1398080808080808080a0236e8f61ecde6abfebc6c529441f782f62469d8a2cc47b7aace2c136bd3b1ff08080808080", 27 | "0xf7a0390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639594de74da73d5102a796559933296c73e7d1c6f37fb" 28 | ] 29 | } 30 | ] 31 | }, 32 | "id": 1 33 | } -------------------------------------------------------------------------------- /__tests__/rlp.mjs: -------------------------------------------------------------------------------- 1 | import { getActor } from "./_common"; 2 | import { ethers } from "ethers"; 3 | import { expect } from "@jest/globals"; 4 | 5 | 6 | const can = await getActor(); 7 | 8 | test("rlp_encode", async () => { 9 | let item = { 10 | values: [ 11 | { Text: "test" }, 12 | { Num: 64 }, 13 | { Empty: null } 14 | ] 15 | } 16 | 17 | let encoded = await can.rlp_encode(item); 18 | // console.log(ethers.utils.hexlify(encoded.Ok)); 19 | let decoded = ethers.utils.RLP.decode(encoded.Ok); 20 | 21 | expect(decoded.length).toBe(3); 22 | }); 23 | 24 | test("rlp_decode", async () => { 25 | let bytes = ethers.utils.arrayify(ethers.utils.hexlify("0xc9845445737481e63201")); 26 | 27 | let decoded = await can.rlp_decode(bytes); 28 | expect(decoded.Ok.values.length).toBe(4); 29 | }); 30 | 31 | 32 | -------------------------------------------------------------------------------- /__tests__/transaction.mjs: -------------------------------------------------------------------------------- 1 | import { getActor } from "./_common"; 2 | import { BigNumber, ethers } from "ethers"; 3 | import { expect } from "@jest/globals"; 4 | 5 | 6 | const can = await getActor(); 7 | 8 | test("create_transaction", async () => { 9 | let arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; 10 | let ten_thousand_as_bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 16]; 11 | 12 | let item = { 13 | Legacy: { 14 | to: ethers.utils.arrayify(ethers.utils.hexlify("0xe94f1fa4f27d9d288ffea234bb62e1fbc086ca0c")), 15 | value: arr, 16 | data: [], 17 | sign: [], 18 | chain_id: 1, 19 | nonce: arr, 20 | gas_limit: ten_thousand_as_bytes, 21 | gas_price: ten_thousand_as_bytes 22 | } 23 | }; 24 | 25 | let encoded = await can.create_transaction(item); 26 | let tx = ethers.utils.parseTransaction(encoded.Ok[0]); //First value is tx 27 | 28 | let hash = ethers.utils.hexlify(encoded.Ok[1]); 29 | 30 | let signing_tx = { 31 | chainId: tx.chainId, 32 | data: tx.data, 33 | to: tx.to, 34 | gasLimit: tx.gasLimit, 35 | gasPrice: tx.gasPrice, 36 | nonce: tx.nonce, 37 | type: tx.type, 38 | value: tx.value 39 | }; 40 | 41 | const raw = ethers.utils.serializeTransaction(signing_tx); // returns RLP encoded tx 42 | const msgHash = ethers.utils.keccak256(raw); 43 | 44 | expect(tx.to.toLocaleLowerCase()).toBe("0xe94f1fa4f27d9d288ffea234bb62e1fbc086ca0c"); 45 | expect(tx.chainId).toStrictEqual(1); 46 | expect(tx.nonce).toStrictEqual(1); 47 | expect(tx.gasLimit).toStrictEqual(BigNumber.from(10_000)); 48 | expect(tx.gasPrice).toStrictEqual(BigNumber.from(10_000)); 49 | 50 | expect(msgHash).toBe(hash); 51 | }); 52 | 53 | test("encode_signed_transaction", async () => { 54 | let arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; 55 | let ten_thousand_as_bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 16]; 56 | 57 | let item = { 58 | Legacy: { 59 | to: ethers.utils.arrayify(ethers.utils.hexlify("0xe94f1fa4f27d9d288ffea234bb62e1fbc086ca0c")), 60 | value: arr, 61 | data: [], 62 | sign: [ 63 | { 64 | r: ethers.utils.arrayify(ethers.utils.hexlify("0xde8ef039a601ad81d0882568e503fd7d865d8e6e03c35f635b301263ce7e00c2")), 65 | s: ethers.utils.arrayify(ethers.utils.hexlify("0x770ceb3615e121d5fa712de1d9b5b27c64dc7ee0610d19307210a6de4b347ad9")), 66 | v: 38, 67 | from: [], 68 | hash: arr 69 | } 70 | ], 71 | chain_id: 1, 72 | nonce: arr, 73 | gas_limit: ten_thousand_as_bytes, 74 | gas_price: ten_thousand_as_bytes 75 | } 76 | }; 77 | 78 | let encoded = await can.encode_signed_transaction(item); 79 | let tx = ethers.utils.parseTransaction(encoded.Ok[0]); //First value is tx 80 | 81 | let hash = ethers.utils.hexlify(encoded.Ok[1]); 82 | 83 | let signing_tx = { 84 | chainId: tx.chainId, 85 | data: tx.data, 86 | to: tx.to, 87 | gasLimit: tx.gasLimit, 88 | gasPrice: tx.gasPrice, 89 | nonce: tx.nonce, 90 | type: tx.type, 91 | value: tx.value 92 | }; 93 | let signature_like = { 94 | r: ethers.utils.hexlify(item.Legacy.sign[0].r), 95 | s: ethers.utils.hexlify(item.Legacy.sign[0].s), 96 | v: item.Legacy.sign[0].v 97 | }; 98 | 99 | const raw = ethers.utils.serializeTransaction(signing_tx, signature_like); // returns RLP encoded tx 100 | const msgHash = ethers.utils.keccak256(raw); 101 | 102 | expect(tx.to.toLocaleLowerCase()).toBe("0xe94f1fa4f27d9d288ffea234bb62e1fbc086ca0c"); 103 | expect(tx.nonce).toStrictEqual(1); 104 | expect(tx.gasLimit).toStrictEqual(BigNumber.from(10_000)); 105 | expect(tx.gasPrice).toStrictEqual(BigNumber.from(10_000)); 106 | expect(tx.chainId).toStrictEqual(item.Legacy.chain_id); 107 | 108 | expect(msgHash).toBe(hash); 109 | }); 110 | 111 | test("parse_transaction", async () => { 112 | let ser = { 113 | to: "0xe94f1fa4f27d9d288ffea234bb62e1fbc086ca0c", 114 | value: 0, 115 | data: [], 116 | chainId: 1, 117 | nonce: 1, 118 | gasPrice: BigNumber.from(10_000), 119 | gasLimit: 1 120 | }; 121 | 122 | let raw_tx = ethers.utils.serializeTransaction(ser); 123 | 124 | let decoded = await can.parse_transaction(ethers.utils.arrayify(raw_tx)); 125 | 126 | expect(decoded.Ok.Legacy).not.toBeUndefined(); 127 | expect(BigNumber.from(decoded.Ok.Legacy.gas_limit)).toStrictEqual(BigNumber.from(1)); 128 | expect(BigNumber.from(decoded.Ok.Legacy.gas_price)).toStrictEqual(BigNumber.from(10_000)); 129 | expect(BigNumber.from(decoded.Ok.Legacy.nonce)).toStrictEqual(BigNumber.from(1)); 130 | expect(BigNumber.from(decoded.Ok.Legacy.chain_id)).toStrictEqual(BigNumber.from(1)); 131 | }); 132 | -------------------------------------------------------------------------------- /__tests__/tree.mjs: -------------------------------------------------------------------------------- 1 | import { getActor } from "./_common"; 2 | import { BigNumber, ethers } from "ethers"; 3 | import { expect } from "@jest/globals"; 4 | 5 | import fs from "fs"; 6 | import { Trie } from "@ethereumjs/trie"; 7 | 8 | const can = await getActor(); 9 | 10 | 11 | test("verify_proof", async () => { 12 | const trie = new Trie({ useKeyHashing: true }) 13 | let data = JSON.parse(fs.readFileSync('./__tests__/proof.json')); 14 | 15 | let storageHash = Buffer.from(ethers.utils.arrayify(data.result.storageHash)); 16 | let storageProof = data.result.storageProof[0]; 17 | let value = storageProof.value; 18 | 19 | let padded_key = ethers.utils.hexZeroPad(storageProof.key, 32); 20 | 21 | //Prepare proof for trie 22 | let proof = []; 23 | for (let i of storageProof.proof) { 24 | proof.push(ethers.utils.arrayify(i)); 25 | } 26 | 27 | let key_bytes = Buffer.from(ethers.utils.arrayify(padded_key)); 28 | const result = await trie.verifyProof(storageHash, key_bytes, proof) 29 | //Output is RLP encoded, decode 30 | let result_data = ethers.utils.RLP.decode(result); 31 | expect(result_data).toBe(value); 32 | 33 | let can_result = await can.verify_proof(storageHash, key_bytes, proof); 34 | //Output is RLP encoded, decode 35 | let hex_result = ethers.utils.RLP.decode(Buffer.from(can_result.Ok[0])); 36 | 37 | expect(hex_result).toBe(value); 38 | }); 39 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | cargo build --target wasm32-unknown-unknown --release 2 | ic-cdk-optimizer ./target/wasm32-unknown-unknown/release/evm_utils.wasm -o ./target/wasm32-unknown-unknown/release/evm_utils_out.wasm -------------------------------------------------------------------------------- /candid/utils.did: -------------------------------------------------------------------------------- 1 | type AccessList = record { storage_keys : vec vec nat8; address : vec nat8 }; 2 | type Item = variant { 3 | Num : nat64; 4 | Raw : vec nat8; 5 | Empty; 6 | List : List; 7 | Text : text; 8 | }; 9 | type List = record { values : vec Item }; 10 | type Result = variant { Ok : record { vec nat8; vec nat8 }; Err : text }; 11 | type Result_1 = variant { Ok; Err : text }; 12 | type Result_2 = variant { Ok : Transaction; Err : text }; 13 | type Result_3 = variant { Ok : vec nat8; Err : text }; 14 | type Result_4 = variant { Ok : List; Err : text }; 15 | type Result_5 = variant { Ok : opt vec nat8; Err : text }; 16 | type Signature = record { 17 | r : vec nat8; 18 | s : vec nat8; 19 | v : nat64; 20 | from : opt vec nat8; 21 | hash : vec nat8; 22 | }; 23 | type Transaction = variant { 24 | EIP1559 : Transaction1559; 25 | EIP2930 : Transaction2930; 26 | Legacy : TransactionLegacy; 27 | }; 28 | type Transaction1559 = record { 29 | to : vec nat8; 30 | value : vec nat8; 31 | max_priority_fee_per_gas : vec nat8; 32 | data : vec nat8; 33 | sign : opt Signature; 34 | max_fee_per_gas : vec nat8; 35 | chain_id : nat64; 36 | nonce : vec nat8; 37 | gas_limit : vec nat8; 38 | access_list : vec AccessList; 39 | }; 40 | type Transaction2930 = record { 41 | to : vec nat8; 42 | value : vec nat8; 43 | data : vec nat8; 44 | sign : opt Signature; 45 | chain_id : nat64; 46 | nonce : vec nat8; 47 | gas_limit : vec nat8; 48 | access_list : vec AccessList; 49 | gas_price : vec nat8; 50 | }; 51 | type TransactionLegacy = record { 52 | to : vec nat8; 53 | value : vec nat8; 54 | data : vec nat8; 55 | sign : opt Signature; 56 | chain_id : nat64; 57 | nonce : vec nat8; 58 | gas_limit : vec nat8; 59 | gas_price : vec nat8; 60 | }; 61 | service : { 62 | create_transaction : (Transaction) -> (Result) query; 63 | encode_signed_transaction : (Transaction) -> (Result) query; 64 | is_valid_public : (vec nat8) -> (Result_1) query; 65 | is_valid_signature : (vec nat8) -> (Result_1) query; 66 | keccak256 : (vec nat8) -> (vec nat8) query; 67 | parse_transaction : (vec nat8) -> (Result_2) query; 68 | pub_to_address : (vec nat8) -> (Result_3) query; 69 | recover_public_key : (vec nat8, vec nat8) -> (Result_3) query; 70 | rlp_decode : (vec nat8) -> (Result_4) query; 71 | rlp_encode : (List) -> (Result_3) query; 72 | verify_proof : (vec nat8, vec nat8, vec vec nat8) -> (Result_5) query; 73 | } -------------------------------------------------------------------------------- /dfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "canisters": { 4 | "evm_utils": { 5 | "type": "rust", 6 | "package": "evm_utils", 7 | "candid": "candid/utils.did" 8 | } 9 | }, 10 | "defaults": { 11 | "build": { 12 | "packtool": "", 13 | "args": "" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /jest.config.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | // All imported modules in your tests should be mocked automatically 8 | // automock: false, 9 | 10 | // Stop running tests after `n` failures 11 | // bail: 0, 12 | 13 | // The directory where Jest should store its cached dependency information 14 | // cacheDirectory: "/tmp/jest_rs", 15 | 16 | // Automatically clear mock calls, instances, contexts and results before every test 17 | // clearMocks: false, 18 | 19 | // Indicates whether the coverage information should be collected while executing the test 20 | collectCoverage: true, 21 | 22 | // An array of glob patterns indicating a set of files for which coverage information should be collected 23 | // collectCoverageFrom: undefined, 24 | 25 | // The directory where Jest should output its coverage files 26 | coverageDirectory: "coverage", 27 | 28 | // An array of regexp pattern strings used to skip coverage collection 29 | // coveragePathIgnorePatterns: [ 30 | // "/node_modules/" 31 | // ], 32 | 33 | // Indicates which provider should be used to instrument code for coverage 34 | coverageProvider: "v8", 35 | 36 | // A list of reporter names that Jest uses when writing coverage reports 37 | // coverageReporters: [ 38 | // "json", 39 | // "text", 40 | // "lcov", 41 | // "clover" 42 | // ], 43 | 44 | // An object that configures minimum threshold enforcement for coverage results 45 | // coverageThreshold: undefined, 46 | 47 | // A path to a custom dependency extractor 48 | // dependencyExtractor: undefined, 49 | 50 | // Make calling deprecated APIs throw helpful error messages 51 | // errorOnDeprecated: false, 52 | 53 | // The default configuration for fake timers 54 | // fakeTimers: { 55 | // "enableGlobally": false 56 | // }, 57 | 58 | // Force coverage collection from ignored files using an array of glob patterns 59 | // forceCoverageMatch: [], 60 | 61 | // A path to a module which exports an async function that is triggered once before all test suites 62 | // globalSetup: undefined, 63 | 64 | // A path to a module which exports an async function that is triggered once after all test suites 65 | // globalTeardown: undefined, 66 | 67 | // A set of global variables that need to be available in all test environments 68 | // globals: {}, 69 | 70 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 71 | // maxWorkers: "50%", 72 | 73 | // An array of directory names to be searched recursively up from the requiring module's location 74 | // moduleDirectories: [ 75 | // "node_modules" 76 | // ], 77 | 78 | // An array of file extensions your modules use 79 | // moduleFileExtensions: [ 80 | // "js", 81 | // "mjs", 82 | // "cjs", 83 | // "jsx", 84 | // "ts", 85 | // "tsx", 86 | // "json", 87 | // "node" 88 | // ], 89 | 90 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 91 | // moduleNameMapper: {}, 92 | 93 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 94 | // modulePathIgnorePatterns: [], 95 | 96 | // Activates notifications for test results 97 | // notify: false, 98 | 99 | // An enum that specifies notification mode. Requires { notify: true } 100 | // notifyMode: "failure-change", 101 | 102 | // A preset that is used as a base for Jest's configuration 103 | // preset: undefined, 104 | 105 | // Run tests from one or more projects 106 | // projects: undefined, 107 | 108 | // Use this configuration option to add custom reporters to Jest 109 | // reporters: undefined, 110 | 111 | // Automatically reset mock state before every test 112 | // resetMocks: false, 113 | 114 | // Reset the module registry before running each individual test 115 | // resetModules: false, 116 | 117 | // A path to a custom resolver 118 | // resolver: undefined, 119 | 120 | // Automatically restore mock state and implementation before every test 121 | // restoreMocks: false, 122 | 123 | // The root directory that Jest should scan for tests and modules within 124 | // rootDir: undefined, 125 | 126 | // A list of paths to directories that Jest should use to search for files in 127 | // roots: [ 128 | // "" 129 | // ], 130 | 131 | // Allows you to use a custom runner instead of Jest's default test runner 132 | // runner: "jest-runner", 133 | 134 | // The paths to modules that run some code to configure or set up the testing environment before each test 135 | // setupFiles: [], 136 | 137 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 138 | // setupFilesAfterEnv: [], 139 | 140 | // The number of seconds after which a test is considered as slow and reported as such in the results. 141 | // slowTestThreshold: 5, 142 | 143 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 144 | // snapshotSerializers: [], 145 | 146 | // The test environment that will be used for testing 147 | testEnvironment: "jest-environment-node", 148 | 149 | // Options that will be passed to the testEnvironment 150 | // testEnvironmentOptions: {}, 151 | 152 | // Adds a location field to test results 153 | // testLocationInResults: false, 154 | 155 | // The glob patterns Jest uses to detect test files 156 | testMatch: [ 157 | "**/__tests__/**/[^_]*.m[jt]s?(x)", 158 | // "**/?(*.)+(spec|test).[tj]s?(x)" 159 | ], 160 | 161 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 162 | // testPathIgnorePatterns: [ 163 | // "/node_modules/" 164 | // ], 165 | 166 | // The regexp pattern or array of patterns that Jest uses to detect test files 167 | // testRegex: [], 168 | 169 | // This option allows the use of a custom results processor 170 | // testResultsProcessor: undefined, 171 | 172 | // This option allows use of a custom test runner 173 | // testRunner: "jest-circus/runner", 174 | 175 | // A map from regular expressions to paths to transformers 176 | transform: {}, 177 | 178 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 179 | // transformIgnorePatterns: [ 180 | // "/node_modules/", 181 | // "\\.pnp\\.[^\\/]+$" 182 | // ], 183 | 184 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 185 | // unmockedModulePathPatterns: undefined, 186 | 187 | // Indicates whether each individual test should be reported during the run 188 | // verbose: undefined, 189 | 190 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 191 | // watchPathIgnorePatterns: [], 192 | 193 | // Whether to use watchman for file crawling 194 | // watchman: true, 195 | }; 196 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bounty_28", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "git@github.com:icopen/evm_utils_ic.git", 6 | "author": "stopak ", 7 | "license": "MIT", 8 | "dependencies": { 9 | "@dfinity/agent": "^0.15.3", 10 | "@dfinity/candid": "^0.15.3", 11 | "@dfinity/principal": "^0.15.3", 12 | "@ethereumjs/trie": "^5.0.2", 13 | "@jest/globals": "^29.4.1", 14 | "crypto-browserify": "^3.12.0", 15 | "ethers": "^5.7.2", 16 | "jest": "^29.4.1", 17 | "lightic": "^0.1.0", 18 | "node-fetch": "^3.3.0" 19 | }, 20 | "type": "module", 21 | "scripts": { 22 | "test": "node --experimental-vm-modules node_modules/.bin/jest", 23 | "build": "cargo build --release --target wasm32-unknown-unknown", 24 | "dfx:run": "USE_DFX=true yarn test", 25 | "dfx:test": "npm run dfx:start && npm run dfx:run && dfx stop", 26 | "dfx:start": "dfx stop && dfx start --clean --background && dfx deploy" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /readme.MD: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This canister "EVM Utility Canister" provides multiple functions required to create and parse EVM compliant transactions. It was created in order to give possibility for Motoko devs to interact with EVM chains. 4 | 5 | Link [https://icdevs.org/bounties/2023/01/09/28-EVM-Utility-Canister-Rust.html] 6 | 7 | Tha canister is published and can be reached under: ubgoy-tiaaa-aaaah-qc7qq-cai 8 | 9 | # Functions 10 | 11 | ## RLP 12 | 13 | - [x] `rlp_encode` - Based on provided data returns rlp encoded bytes 14 | - [x] `rlp_decode` - decodes RLP bytes in to an object. 15 | 16 | 17 | ## Transaction 18 | 19 | - [x] `create_transaction` - Works with an unsigned transaction, returns encoded transaction bytes and hash that is used for signing the transaction 20 | - [x] `parse_transaction` - Works with both signed and non signed transactions, returns object of decoded transaction. Calculates hash and from for transaction (sender is not included, however it can be recovered from transaction data and signature) 21 | 22 | ## EVM Verification 23 | 24 | - [x] `verify_proof` - Verifies if send hash is a part of a tree, returns retrieved data from proof. You need to send a root hash, proof and key. Return is RLP encoded, so you need to decode it to check the output. 25 | 26 | ## Hashing 27 | 28 | - [x] `keccak256` - Hashes incoming data using keccak and returns hash 29 | 30 | ## Utils 31 | For now I have decided to skip functions related to private keys processing. I do believe it is not secure to pass public keys. 32 | 33 | - [x] `recover_public_key` - recovers public key from ethereum style recoverable signature (65 bytes), equivalent to ecrecover 34 | - [x] `is_valid_public` - checks if public key is valid 35 | - [x] `is_valid_signature` - validates ECDSA signature 36 | - [x] `pub_to_address` - converts public key to ethereum address 37 | 38 | # Getting started 39 | This package was published as a cargo crate, to use it type 40 | ``` 41 | cargo add ic_evm_utils 42 | ```` 43 | 44 | If you want to compile the package, clone the repo and run 45 | ``` 46 | cargo build --target wasm32-unknown-unknown 47 | ``` 48 | 49 | There is also prepared build script `build.sh` that builds and optimizes the wasm package using `ic-cdk-optimizer` (you need to install this first) 50 | 51 | # Testing 52 | There are two types of tests prepared. First they are written in Rust to check if in rust everything matches our assumptions. Then there are JS tests that validate output of prepared functions against battle tested ethers library. 53 | 54 | ## Rust tests 55 | To run tests written in Rust 56 | ``` 57 | cargo test 58 | ``` 59 | 60 | ## Js tests 61 | First you need to run local replica 62 | ``` 63 | dfx start --clean --background 64 | ``` 65 | 66 | Then you need to deploy canisters 67 | ``` 68 | dfx deploy 69 | ``` 70 | 71 | Once deployment is complete you can run tests in js 72 | ``` 73 | yarn test 74 | ``` 75 | 76 | # Funding 77 | This library was initially incentivized by [ICDevs](https://icdevs.org/). You can view more about the bounty on the [website](https://icdevs.org/bounties/2023/01/09/28-EVM-Utility-Canister-Rust.html). -------------------------------------------------------------------------------- /src/evm_utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "evm_utils" 3 | version = "0.1.0" 4 | edition = "2021" 5 | repository = "https://github.com/icopen/evm_utils_ic" 6 | license = "MIT" 7 | keywords = ["ic", "evm"] 8 | description = "A utility canister for Internet Computer, allows other canisters to call functions related to EVM" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [lib] 13 | crate-type = ["cdylib"] 14 | 15 | [dependencies] 16 | bytes = "1.3.0" 17 | candid = "0.8.2" 18 | cita_trie = "4.0.0" 19 | hex = "0.4.3" 20 | ic-cdk = "0.6.0" 21 | ic-cdk-macros = "0.6.0" 22 | rlp = "0.5.2" 23 | secp256k1 = { version = "0.26.0", features = [ 24 | "global-context", 25 | "recovery", 26 | ] } 27 | hasher = { version = "0.1", features = ["hash-keccak"] } 28 | serde = "1.0.152" 29 | sha3 = "0.10.6" 30 | trie-db = "0.25.0" 31 | -------------------------------------------------------------------------------- /src/evm_utils/src/hash.rs: -------------------------------------------------------------------------------- 1 | use crate::{types::num::U256, utils}; 2 | use candid::candid_method; 3 | use ic_cdk::query; 4 | 5 | /// Returns hash of given data using keccak256 6 | #[query] 7 | #[candid_method(query)] 8 | fn keccak256(data: Vec) -> U256 { 9 | utils::keccak256(&[&data[..]]) 10 | } 11 | -------------------------------------------------------------------------------- /src/evm_utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | use candid::export_service; 2 | use ic_cdk::query; 3 | 4 | mod hash; 5 | mod rlp; 6 | mod transaction; 7 | mod tree; 8 | mod types; 9 | mod utils; 10 | 11 | use crate::types::num::U256; 12 | use crate::types::rlp::List; 13 | use crate::types::transaction::Transaction; 14 | 15 | #[query(name = "__get_candid_interface_tmp_hack")] 16 | fn export_candid() -> String { 17 | export_service!(); 18 | __export_service() 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use super::*; 24 | 25 | #[test] 26 | fn save_candid() { 27 | use std::env; 28 | use std::fs::write; 29 | use std::path::PathBuf; 30 | 31 | let dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); 32 | let dir = dir.parent().unwrap().parent().unwrap().join("candid"); 33 | write(dir.join("utils.did"), export_candid()).expect("Write failed."); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/evm_utils/src/rlp.rs: -------------------------------------------------------------------------------- 1 | use crate::types::rlp::List; 2 | 3 | use candid::candid_method; 4 | use ic_cdk_macros::query; 5 | 6 | #[query] 7 | #[candid_method(query)] 8 | fn rlp_encode(data: List) -> Result, String> { 9 | let raw = rlp::encode(&data); 10 | 11 | Ok(raw.to_vec()) 12 | } 13 | 14 | #[query] 15 | #[candid_method(query)] 16 | fn rlp_decode(raw: Vec) -> Result { 17 | let item: List = rlp::decode(&raw).map_err(|x| format!("{x}"))?; 18 | 19 | Ok(item) 20 | } 21 | 22 | #[cfg(test)] 23 | mod test { 24 | use crate::{ 25 | rlp::{rlp_decode, rlp_encode}, 26 | types::rlp::{Item, List}, 27 | }; 28 | 29 | #[test] 30 | fn encode_decode_test() -> Result<(), String> { 31 | let item = List { 32 | values: vec![Item::Text(String::from("test")), Item::Num(64), Item::Empty], 33 | }; 34 | 35 | let encoded = rlp_encode(item.clone())?; 36 | 37 | let hex_encoded = hex::encode(&encoded); 38 | println!("{hex_encoded}"); 39 | 40 | let decoded = rlp_decode(encoded)?; 41 | 42 | assert_eq!(item.values.len(), decoded.values.len()); 43 | 44 | Ok(()) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/evm_utils/src/transaction.rs: -------------------------------------------------------------------------------- 1 | use candid::candid_method; 2 | use ic_cdk_macros::query; 3 | 4 | use crate::{ 5 | types::{num::U256, transaction::Transaction}, 6 | utils::keccak256, 7 | }; 8 | 9 | /// Encodes transaction in rlp, ready to be signed 10 | #[query] 11 | #[candid_method(query)] 12 | fn create_transaction(data: Transaction) -> Result<(Vec, U256), String> { 13 | let raw = data.encode(true); 14 | let hash = keccak256(&[&raw]); 15 | 16 | Ok((raw.to_vec(), hash)) 17 | } 18 | 19 | /// Encodes transaction in rlp, including the signature data 20 | #[query] 21 | #[candid_method(query)] 22 | fn encode_signed_transaction(data: Transaction) -> Result<(Vec, U256), String> { 23 | let raw = data.encode(false); 24 | let hash = keccak256(&[&raw]); 25 | 26 | Ok((raw.to_vec(), hash)) 27 | } 28 | 29 | /// Parses raw transaction, supports Legacy, EIP1559, EIP2930 30 | #[query] 31 | #[candid_method(query)] 32 | fn parse_transaction(data: Vec) -> Result { 33 | let item = 34 | Transaction::decode(&data).map_err(|x| format!("Error while decoding transaction {x}"))?; 35 | 36 | Ok(item) 37 | } 38 | -------------------------------------------------------------------------------- /src/evm_utils/src/tree.rs: -------------------------------------------------------------------------------- 1 | use candid::candid_method; 2 | use hasher::HasherKeccak; 3 | use ic_cdk::query; 4 | 5 | use crate::utils::keccak256; 6 | 7 | #[query] 8 | #[candid_method(query)] 9 | fn verify_proof( 10 | root: Vec, 11 | key: Vec, 12 | proof: Vec>, 13 | ) -> Result>, String> { 14 | let hasher = HasherKeccak::new(); 15 | let hashed_key = keccak256(&[&key]); 16 | 17 | let data = cita_trie::verify_proof(&root, &hashed_key.0, proof, hasher) 18 | .map_err(|x| format!("Error while verifying proof {x}"))?; 19 | 20 | Ok(data) 21 | } 22 | 23 | #[cfg(test)] 24 | mod test { 25 | use crate::{types::num::U256, utils::keccak256}; 26 | use hasher::HasherKeccak; 27 | use std::error::Error; 28 | use std::fmt::{self, Display}; 29 | 30 | #[derive(Debug)] 31 | pub enum TestError { 32 | NotFound, 33 | } 34 | 35 | impl std::error::Error for TestError {} 36 | impl Display for TestError { 37 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 38 | f.write_str(&format!("{self:?}"))?; 39 | Ok(()) 40 | } 41 | } 42 | 43 | #[test] 44 | fn verify_proof_test() -> Result<(), Box> { 45 | let root = hex::decode("7317ebbe7d6c43dd6944ed0e2c5f79762113cb75fa0bed7124377c0814737fb4")?; 46 | let proof = vec![ 47 | hex::decode("f8518080a0cd2a98a2ebb71b70e1109bf206dbc96dc73c76569b42df09ff269ecdcd31b1398080808080808080a0236e8f61ecde6abfebc6c529441f782f62469d8a2cc47b7aace2c136bd3b1ff08080808080")?, 48 | hex::decode("f7a0390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639594de74da73d5102a796559933296c73e7d1c6f37fb")?, 49 | ]; 50 | let hasher = HasherKeccak::new(); 51 | 52 | let pos = U256::zero(); 53 | let hashed_pos = keccak256(&[&pos.0]); 54 | 55 | let result = cita_trie::verify_proof(&root, &hashed_pos.0, proof, hasher)?; 56 | 57 | match result { 58 | Some(_) => Ok(()), 59 | None => Err(Box::new(TestError::NotFound)), 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/evm_utils/src/types/access_list.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::export::candid::{CandidType, Deserialize}; 2 | use rlp::{Decodable, Encodable, RlpStream}; 3 | 4 | use super::{address::Address, num::H256}; 5 | 6 | #[derive(CandidType, Deserialize, Clone, PartialEq, Eq)] 7 | pub struct AccessList { 8 | pub address: Address, 9 | pub storage_keys: Vec, 10 | } 11 | 12 | impl Decodable for AccessList { 13 | fn decode(rlp: &rlp::Rlp) -> Result { 14 | let item_count = rlp.item_count()?; 15 | if item_count != 2 { 16 | return Err(rlp::DecoderError::Custom( 17 | "Invalid parameters for Access List", 18 | )); 19 | } 20 | 21 | let address = rlp.val_at(0)?; 22 | let storage_keys = rlp.list_at(1)?; 23 | Ok(Self { 24 | address, 25 | storage_keys, 26 | }) 27 | } 28 | } 29 | 30 | impl Encodable for AccessList { 31 | fn rlp_append(&self, rlp: &mut RlpStream) { 32 | rlp.begin_list(2); 33 | rlp.append(&self.address); 34 | rlp.append_list(&self.storage_keys); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/evm_utils/src/types/address.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use ic_cdk::export::candid::{CandidType, Deserialize}; 4 | use rlp::{Decodable, DecoderError, Encodable, RlpStream}; 5 | use secp256k1::PublicKey; 6 | 7 | use crate::utils::keccak256; 8 | 9 | #[derive(CandidType, Deserialize, Clone, PartialEq, Eq)] 10 | pub struct Address(pub [u8; 20]); 11 | 12 | impl Decodable for Address { 13 | fn decode(rlp: &rlp::Rlp) -> Result { 14 | let data = rlp.data()?; 15 | if data.len() != 20 { 16 | return Err(DecoderError::Custom( 17 | "Invalid number of bytes for ETH address", 18 | )); 19 | } 20 | 21 | let mut buf = [0u8; 20]; 22 | buf.copy_from_slice(&data[..20]); 23 | Ok(Self(buf)) 24 | } 25 | } 26 | 27 | impl Encodable for Address { 28 | fn rlp_append(&self, rlp: &mut RlpStream) { 29 | rlp.append_internal(&self.0.to_vec()); 30 | } 31 | } 32 | 33 | impl Display for Address { 34 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 35 | write!(f, "0x")?; 36 | write!(f, "{}", hex::encode(self.0))?; 37 | Ok(()) 38 | } 39 | } 40 | 41 | impl From for Address { 42 | fn from(from: PublicKey) -> Self { 43 | let addr = &keccak256(&[&from.serialize_uncompressed()[1..]]).0[12..]; 44 | let mut buf = [0u8; 20]; 45 | buf.copy_from_slice(&addr[..20]); 46 | 47 | Self(buf) 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod test { 53 | use std::error::Error; 54 | use std::str::FromStr; 55 | 56 | use secp256k1::PublicKey; 57 | 58 | use super::Address; 59 | 60 | #[test] 61 | fn test_public_key_to_address() -> Result<(), Box> { 62 | let key = PublicKey::from_str("04da2bd30515dc22663fa0fd96e3a866b6858876f83d990953065cacf1fa6de3e441e85d533c3cb7f7fb94a73dbb01c22b9340ef4a9940c6a81adea958effe0c8a")?; 63 | let addr = Address::from(key); 64 | 65 | let addr_str = format!("{}", addr); 66 | 67 | assert_eq!(addr_str, "0x690b9a9e9aa1c9db991c7721a92d351db4fac990"); 68 | 69 | Ok(()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/evm_utils/src/types/errors.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | 3 | #[derive(Debug)] 4 | pub enum TransactionError { 5 | InvalidType, 6 | } 7 | 8 | impl std::error::Error for TransactionError {} 9 | 10 | impl Display for TransactionError { 11 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 12 | f.write_str(&format!("{self:?}"))?; 13 | Ok(()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/evm_utils/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod access_list; 2 | pub mod address; 3 | pub mod errors; 4 | pub mod num; 5 | pub mod rlp; 6 | pub mod signature; 7 | pub mod transaction; 8 | pub mod transaction_1559; 9 | pub mod transaction_2930; 10 | pub mod transaction_legacy; 11 | -------------------------------------------------------------------------------- /src/evm_utils/src/types/num.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use ic_cdk::export::candid::{CandidType, Deserialize}; 4 | use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; 5 | 6 | #[derive(CandidType, Deserialize, Clone, PartialEq, Eq, Debug)] 7 | pub struct U256(pub [u8; 32]); 8 | impl U256 { 9 | pub fn zero() -> Self { 10 | Self([0u8; 32]) 11 | } 12 | } 13 | 14 | #[derive(CandidType, Deserialize, Clone, PartialEq, Eq, Debug)] 15 | pub struct H256(pub [u8; 32]); 16 | impl H256 { 17 | pub fn zero() -> Self { 18 | Self([0u8; 32]) 19 | } 20 | } 21 | 22 | impl From<[u8; 32]> for U256 { 23 | #[inline] 24 | fn from(bytes: [u8; 32]) -> Self { 25 | U256(bytes) 26 | } 27 | } 28 | 29 | impl<'a> From<&'a [u8; 32]> for U256 { 30 | #[inline] 31 | fn from(bytes: &'a [u8; 32]) -> Self { 32 | U256(*bytes) 33 | } 34 | } 35 | 36 | impl From for U256 { 37 | #[inline] 38 | fn from(num: u64) -> Self { 39 | let mut buf = [0u8; 32]; 40 | buf[24..32].copy_from_slice(&num.to_be_bytes()); 41 | U256(buf) 42 | } 43 | } 44 | 45 | impl Display for U256 { 46 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 47 | write!(f, "0x")?; 48 | write!(f, "{}", hex::encode(self.0)) 49 | } 50 | } 51 | 52 | impl Encodable for U256 { 53 | fn rlp_append(&self, s: &mut RlpStream) { 54 | // s.encoder().encode_value(&self.0); 55 | let mut size = 0; 56 | for byte in self.0.iter() { 57 | if *byte == 0 { 58 | size += 1; 59 | } else { 60 | break; 61 | } 62 | } 63 | 64 | let out = &self.0[size..]; 65 | 66 | s.encoder().encode_value(out); 67 | } 68 | } 69 | 70 | impl Decodable for U256 { 71 | fn decode(rlp: &Rlp) -> Result { 72 | rlp.decoder().decode_value(|bytes| match bytes.len() { 73 | 0 => Ok(U256::zero()), 74 | l if l <= 32 => { 75 | let mut res = U256::zero(); 76 | 77 | for (i, byte) in bytes.iter().enumerate().take(l) { 78 | res.0[32 - l + i] = *byte; 79 | } 80 | Ok(res) 81 | } 82 | _ => Err(DecoderError::RlpIsTooBig), 83 | }) 84 | } 85 | } 86 | 87 | impl Encodable for H256 { 88 | fn rlp_append(&self, s: &mut RlpStream) { 89 | s.encoder().encode_value(&self.0); 90 | } 91 | } 92 | 93 | impl Decodable for H256 { 94 | fn decode(rlp: &Rlp) -> Result { 95 | rlp.decoder().decode_value(|bytes| match bytes.len() { 96 | 0 => Ok(H256::zero()), 97 | l if l <= 32 => { 98 | let mut res = H256::zero(); 99 | 100 | for (i, byte) in bytes.iter().enumerate().take(l) { 101 | res.0[32 - l + i] = *byte; 102 | } 103 | Ok(res) 104 | } 105 | _ => Err(DecoderError::RlpIsTooBig), 106 | }) 107 | } 108 | } 109 | 110 | #[cfg(test)] 111 | mod test { 112 | use std::error::Error; 113 | 114 | use super::U256; 115 | 116 | #[test] 117 | fn from_u64() { 118 | let num = 1024u64; 119 | 120 | let converted = U256::from(num); 121 | assert_eq!(converted.0[30], 4); 122 | } 123 | 124 | #[test] 125 | fn rlp_encode_zero() { 126 | let num = U256::from(10u64); 127 | let encoded = rlp::encode(&num); 128 | 129 | let hex_data = hex::encode(&encoded); 130 | 131 | println!("{hex_data}"); 132 | } 133 | 134 | #[test] 135 | fn rlp_encode_decode() -> Result<(), Box> { 136 | let num = U256::from(1000_000u64); 137 | let encoded = rlp::encode(&num); 138 | let decoded: U256 = rlp::decode(&encoded)?; 139 | 140 | // let hex_data = hex::encode(&encoded); 141 | 142 | // println!("{hex_data}"); 143 | 144 | assert_eq!(num, decoded); 145 | 146 | Ok(()) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/evm_utils/src/types/rlp.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::export::candid::{CandidType, Deserialize}; 2 | use rlp::{Decodable, Encodable, RlpStream}; 3 | 4 | #[derive(CandidType, Deserialize, Clone)] 5 | pub enum Item { 6 | Text(String), 7 | Num(u64), 8 | List(List), 9 | Raw(Vec), 10 | Empty, 11 | } 12 | 13 | impl Decodable for Item { 14 | fn decode(rlp: &rlp::Rlp) -> Result { 15 | let mut item = Item::Empty; 16 | 17 | if rlp.is_list() { 18 | let i: List = rlp.as_val()?; 19 | item = Item::List(i); 20 | } 21 | if rlp.is_int() { 22 | let i: u64 = rlp.as_val()?; 23 | item = Item::Num(i); 24 | } 25 | if rlp.is_data() { 26 | let i: Vec = rlp.as_val()?; 27 | item = Item::Raw(i); 28 | } 29 | 30 | Ok(item) 31 | } 32 | } 33 | 34 | impl Encodable for Item { 35 | fn rlp_append(&self, rlp: &mut RlpStream) { 36 | match self { 37 | Item::Text(txt) => { 38 | rlp.append(txt); 39 | } 40 | Item::Num(txt) => { 41 | rlp.append(txt); 42 | } 43 | Item::List(txt) => { 44 | rlp.append(txt); 45 | } 46 | Item::Raw(txt) => { 47 | rlp.append(txt); 48 | } 49 | Item::Empty => { 50 | rlp.append_empty_data(); 51 | } 52 | } 53 | } 54 | } 55 | 56 | #[derive(CandidType, Deserialize, Clone)] 57 | pub struct List { 58 | pub values: Vec, 59 | } 60 | 61 | impl Decodable for List { 62 | fn decode(rlp: &rlp::Rlp) -> Result { 63 | if !rlp.is_list() { 64 | return Err(rlp::DecoderError::RlpExpectedToBeList); 65 | } 66 | 67 | let mut item = Self { values: vec![] }; 68 | 69 | for i in rlp.into_iter() { 70 | let data = i.as_val::()?; 71 | item.values.push(data); 72 | } 73 | 74 | Ok(item) 75 | } 76 | } 77 | 78 | impl Encodable for List { 79 | fn rlp_append(&self, rlp: &mut RlpStream) { 80 | // rlp.append_list(&self.values); 81 | rlp.begin_unbounded_list(); 82 | 83 | for item in &self.values { 84 | rlp.append(item); 85 | } 86 | 87 | rlp.finalize_unbounded_list(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/evm_utils/src/types/signature.rs: -------------------------------------------------------------------------------- 1 | use super::{address::Address, num::U256}; 2 | use crate::utils::{_recover_public_key, keccak256}; 3 | use bytes::BytesMut; 4 | use ic_cdk::export::candid::{CandidType, Deserialize}; 5 | use rlp::RlpStream; 6 | use std::{error::Error, vec}; 7 | 8 | #[derive(CandidType, Deserialize, Clone, PartialEq, Eq)] 9 | pub struct Signature { 10 | pub v: u64, 11 | pub r: Vec, 12 | pub s: Vec, 13 | pub from: Option
, 14 | pub hash: U256, 15 | } 16 | 17 | impl Signature { 18 | pub fn create( 19 | tx: &dyn Signable, 20 | rlp: &rlp::Rlp, 21 | msg: &[u8], 22 | position: usize, 23 | ) -> Result> { 24 | let v: u64 = rlp.val_at(position)?; 25 | let r: Vec = rlp.val_at(position + 1)?; 26 | let s: Vec = rlp.val_at(position + 2)?; 27 | 28 | let bytes = tx.get_bytes(true); 29 | 30 | let sender = _recover_public_key(&r, &s, v, &bytes)?; 31 | let from = Some(Address::from(sender)); 32 | 33 | let hash = keccak256(&[msg]); 34 | 35 | Ok(Self { 36 | from, 37 | v, 38 | r, 39 | s, 40 | hash, 41 | }) 42 | } 43 | } 44 | 45 | pub trait Signable { 46 | fn get_bytes(&self, for_signing: bool) -> BytesMut; 47 | fn encode_rlp(&self, rlp: &mut RlpStream, for_signing: bool); 48 | } 49 | -------------------------------------------------------------------------------- /src/evm_utils/src/types/transaction.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, vec}; 2 | 3 | use bytes::BytesMut; 4 | use ic_cdk::export::candid::{CandidType, Deserialize}; 5 | 6 | use super::errors::TransactionError; 7 | use super::signature::Signable; 8 | use super::transaction_1559::Transaction1559; 9 | use super::transaction_2930::Transaction2930; 10 | use super::transaction_legacy::TransactionLegacy; 11 | 12 | #[derive(CandidType, Deserialize, Clone, PartialEq, Eq)] 13 | pub enum Transaction { 14 | Legacy(TransactionLegacy), 15 | EIP1559(Transaction1559), 16 | EIP2930(Transaction2930), 17 | } 18 | 19 | impl Transaction { 20 | pub fn decode(hex_raw_tx: &[u8]) -> Result> { 21 | if hex_raw_tx[0] > 0x7f { 22 | Ok(Self::Legacy(rlp::decode(hex_raw_tx)?)) 23 | } else if hex_raw_tx[0] == 0x01 { 24 | Ok(Self::EIP2930(rlp::decode(&hex_raw_tx[1..])?)) 25 | } else if hex_raw_tx[0] == 0x02 { 26 | Ok(Self::EIP1559(rlp::decode(&hex_raw_tx[1..])?)) 27 | } else { 28 | Err(Box::new(TransactionError::InvalidType)) 29 | } 30 | } 31 | 32 | pub fn encode(&self, for_signing: bool) -> BytesMut { 33 | match self { 34 | Transaction::Legacy(a) => a.get_bytes(for_signing), 35 | Transaction::EIP1559(a) => a.get_bytes(for_signing), 36 | Transaction::EIP2930(a) => a.get_bytes(for_signing), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/evm_utils/src/types/transaction_1559.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, BytesMut}; 2 | use ic_cdk::export::candid::{CandidType, Deserialize}; 3 | use rlp::{Decodable, RlpStream}; 4 | 5 | use super::{ 6 | access_list::AccessList, 7 | address::Address, 8 | num::U256, 9 | signature::{Signable, Signature}, 10 | }; 11 | 12 | #[derive(CandidType, Deserialize, Clone, PartialEq, Eq)] 13 | pub struct Transaction1559 { 14 | pub chain_id: u64, 15 | pub nonce: U256, 16 | pub max_priority_fee_per_gas: U256, 17 | pub gas_limit: U256, 18 | pub max_fee_per_gas: U256, 19 | pub to: Address, 20 | pub value: U256, 21 | pub data: Vec, 22 | pub access_list: Vec, 23 | pub sign: Option, 24 | } 25 | 26 | impl Decodable for Transaction1559 { 27 | fn decode(rlp: &rlp::Rlp) -> Result { 28 | let item_count = rlp.item_count()?; 29 | if item_count != 9 && item_count != 12 { 30 | return Err(rlp::DecoderError::Custom( 31 | "Invalid parameters for 1559 transaction", 32 | )); 33 | } 34 | 35 | let chain_id: u64 = rlp.val_at(0)?; 36 | let nonce: U256 = rlp.val_at(1)?; 37 | 38 | let max_priority_fee_per_gas: U256 = rlp.val_at(2)?; 39 | let max_fee_per_gas: U256 = rlp.val_at(3)?; 40 | let gas_limit: U256 = rlp.val_at(4)?; 41 | 42 | let to = rlp.val_at(5)?; 43 | 44 | let value: U256 = rlp.val_at(6)?; 45 | let data: Vec = rlp.val_at(7)?; 46 | let access_list = rlp.list_at(8)?; 47 | 48 | let mut item = Self { 49 | chain_id, 50 | nonce, 51 | max_priority_fee_per_gas, 52 | max_fee_per_gas, 53 | gas_limit, 54 | to, 55 | value, 56 | data, 57 | access_list, 58 | sign: None, 59 | }; 60 | 61 | if item_count == 12 { 62 | let mut buf = BytesMut::new(); 63 | buf.extend_from_slice(&[2]); 64 | buf.extend_from_slice(rlp.as_raw()); 65 | 66 | let signature = Signature::create(&item, rlp, &buf, 9) 67 | .map_err(|_| rlp::DecoderError::Custom("Error while recovering signature"))?; 68 | 69 | item.sign = Some(signature); 70 | } 71 | 72 | Ok(item) 73 | } 74 | } 75 | 76 | impl Signable for Transaction1559 { 77 | fn get_bytes(&self, for_signing: bool) -> bytes::BytesMut { 78 | let mut rlp = RlpStream::new(); 79 | self.encode_rlp(&mut rlp, for_signing); 80 | 81 | let mut buf: BytesMut = BytesMut::new(); 82 | buf.put_u8(2u8); //write EIP2930 identifier 83 | buf.extend_from_slice(rlp.out().as_ref()); 84 | 85 | buf 86 | } 87 | 88 | fn encode_rlp(&self, rlp: &mut RlpStream, for_signing: bool) { 89 | rlp.begin_unbounded_list(); 90 | 91 | rlp.append(&self.chain_id); 92 | rlp.append(&self.nonce); 93 | rlp.append(&self.max_priority_fee_per_gas); 94 | rlp.append(&self.max_fee_per_gas); 95 | rlp.append(&self.gas_limit); 96 | rlp.append(&self.to); 97 | rlp.append(&self.value); 98 | rlp.append(&self.data); 99 | rlp.append_list(&self.access_list); 100 | 101 | if !for_signing && self.sign.is_some() { 102 | if let Some(sign) = self.sign.as_ref() { 103 | rlp.append(&sign.v); 104 | rlp.append(&sign.r); 105 | rlp.append(&sign.s); 106 | } 107 | } 108 | 109 | rlp.finalize_unbounded_list(); 110 | } 111 | } 112 | 113 | #[cfg(test)] 114 | mod test { 115 | use crate::types::transaction::Transaction; 116 | use std::error::Error; 117 | 118 | #[test] 119 | fn decode_1559_transaction() -> Result<(), Box> { 120 | let data = "0x02f8b10108840f7f4900850519f0118083055845943fe65692bfcd0e6cf84cb1e7d24108e434a7587e80b8447050ccd90000000000000000000000005b1578681d43931030fffe066a072133842dde430000000000000000000000000000000000000000000000000000000000000001c001a0cac74d8e73874331e4ba78809a7b30574c76d35c43ea00983c76752b65a0632aa04b6caeac4c693f082b8bd4d2ad349848bc71b9d32a419e33a5a303a3b3f4e8c8"; 121 | let data = hex::decode(data.trim_start_matches("0x"))?; 122 | 123 | let tx = Transaction::decode(&data)?; 124 | 125 | match tx { 126 | Transaction::EIP1559(x) => match x.sign { 127 | Some(sig) => { 128 | assert_eq!( 129 | "0x5b1578681d43931030fffe066a072133842dde43", 130 | format!("{}", sig.from.unwrap()) 131 | ); 132 | assert_eq!( 133 | "0x89f35f37f590d0f22b5ab8287bcd2d8a47683ef282b6ad4b2552ab01931faf36", 134 | format!("{}", sig.hash) 135 | ); 136 | Ok(()) 137 | } 138 | None => panic!("Missing signature"), 139 | }, 140 | _ => panic!("Wrong transaction type"), 141 | } 142 | } 143 | 144 | #[test] 145 | fn encode_1559_transaction() -> Result<(), Box> { 146 | let data_hex = "0x02f8b10108840f7f4900850519f0118083055845943fe65692bfcd0e6cf84cb1e7d24108e434a7587e80b8447050ccd90000000000000000000000005b1578681d43931030fffe066a072133842dde430000000000000000000000000000000000000000000000000000000000000001c001a0cac74d8e73874331e4ba78809a7b30574c76d35c43ea00983c76752b65a0632aa04b6caeac4c693f082b8bd4d2ad349848bc71b9d32a419e33a5a303a3b3f4e8c8"; 147 | let data = hex::decode(data_hex.trim_start_matches("0x"))?; 148 | 149 | let tx = Transaction::decode(&data)?; 150 | let encoded = tx.encode(false).to_vec(); 151 | let encoded_hex = format!("0x{}", hex::encode(&encoded)); 152 | 153 | assert_eq!(data, encoded); 154 | assert_eq!(data_hex, &encoded_hex); 155 | 156 | Ok(()) 157 | } 158 | 159 | #[test] 160 | fn encode_1559_access_list_transaction() -> Result<(), Box> { 161 | let data_hex = "0x02f9051901820a66844d502d158503ee8a737083035d6f94000000001d68ffe32f650281ff45ffcb93b055a580b8ab54abfa6b839246c4804ca91538ac72cd41bcf05424e6f19dab7d43317344282f803f8e8d240708174a0000000002039ae5e27cddc6ac7b00634ed9079d3ad33d538c3ef57a0753fb16a59390afaf5dff4b2ce30a945708fd2481000000000000041e05d2f4491ba6cdf7028ceab81fa0c6971208e83fa7872994bee58167a1117691f39e05e9131cfa88f0e3a620e96700020000000000000000038c22000000000000042a35b8ac8ec191f903fdf89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f884a02cacd6ecd88e67356ddc78a780214f12b7a52f32434e36086bfdd8bbcfb7d14ba0c1de08830db2749ad8959eb14dca5d9786ce9ea39984eacff65e28a820266c26a01cdd5a182385e6cc1498df081ca576bd53a4b19dda3766a3406201a11cf7df65a03a0f132b80eb284c2ac247698d2ec7dce851ca420b700161ad6082d0e9762150d6940000000000000000000000000000000000000000c0f8dd94e6f19dab7d43317344282f803f8e8d240708174af8c6a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000af8bc94abfa6b839246c4804ca91538ac72cd41bcf05424f8a5a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000002a054cdd369e4e8a8515e52ca72ec816c2101831ad1f18bf44102ed171459c9b4f8a00000000000000000000000000000000000000000000000000000000000000008f8599485eee30c52b0b379b046fb0f85f4f3dc3009afecf842a0c352fe3b9a5f3808969fc7a7de264ff5d985b2a163a73a6abee6ab1e1d15c9b9a0f219792bec970abebde2df1ee9a3fe8857e90e72c7b96368ba32b5d95d5cfd08f89b94cdf7028ceab81fa0c6971208e83fa7872994bee5f884a08b2ec926c2eb426b31c4aa21d946e23b854538f661a329c166b20dcfe4e30ec6a000c8e0a349c9e5bcfca3764b4866e1064734ff0916b8421d0343452c1701ad9da09317159d45a07e1cbf5e4dee1b8045dec3a0b69d5d8c1e5d0437a196007fa571a044be98e9a38b847ef1a5267a6675b71529ec05a942cc75553470b282e3812623f87a94ba12222222228d8ba445958a75a0704d566bf2c8f863a00000000000000000000000000000000000000000000000000000000000000000a09f33ba43609b1caef7a94c0433db35ceb04b7bf53a0fdacaee330a0749cce9aea09f33ba43609b1caef7a94c0433db35ceb04b7bf53a0fdacaee330a0749cce9aff7948167a1117691f39e05e9131cfa88f0e3a620e967e1a0000000000000000000000000000000000000000000000000000000000000000701a0106c2b96f3d56f31d865dae7dca3f1136603fd3de16fa717f9d6b27dda0f8ca1a05cd96b6c47baa11e18acce098b35800ca6de511973721fc5f5c9db4cadcc79b0"; 162 | let data = hex::decode(data_hex.trim_start_matches("0x"))?; 163 | 164 | let tx = Transaction::decode(&data)?; 165 | let encoded = tx.encode(false).to_vec(); 166 | let encoded_hex = format!("0x{}", hex::encode(&encoded)); 167 | 168 | assert_eq!(data_hex, &encoded_hex); 169 | assert_eq!(data, encoded); 170 | 171 | match tx { 172 | Transaction::EIP1559(x) => { 173 | assert_eq!(x.access_list.len(), 8); 174 | Ok(()) 175 | } 176 | _ => panic!("Wrong transaction type"), 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/evm_utils/src/types/transaction_2930.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, BytesMut}; 2 | use ic_cdk::export::candid::{CandidType, Deserialize}; 3 | use rlp::{Decodable, RlpStream}; 4 | 5 | use super::{ 6 | access_list::AccessList, 7 | address::Address, 8 | num::U256, 9 | signature::{Signable, Signature}, 10 | }; 11 | 12 | #[derive(CandidType, Deserialize, Clone, PartialEq, Eq)] 13 | pub struct Transaction2930 { 14 | pub chain_id: u64, 15 | pub nonce: U256, 16 | pub gas_price: U256, 17 | pub gas_limit: U256, 18 | pub to: Address, 19 | pub value: U256, 20 | pub data: Vec, 21 | pub access_list: Vec, 22 | pub sign: Option, 23 | } 24 | 25 | impl Decodable for Transaction2930 { 26 | fn decode(rlp: &rlp::Rlp) -> Result { 27 | let item_count = rlp.item_count()?; 28 | if item_count != 8 && item_count != 11 { 29 | return Err(rlp::DecoderError::Custom( 30 | "Invalid parameters for 2930 transaction", 31 | )); 32 | } 33 | 34 | let chain_id: u64 = rlp.val_at(0)?; 35 | let nonce: U256 = rlp.val_at(1)?; 36 | 37 | let gas_price: U256 = rlp.val_at(2)?; 38 | let gas_limit: U256 = rlp.val_at(3)?; 39 | 40 | let to = rlp.val_at(4)?; 41 | 42 | let value: U256 = rlp.val_at(5)?; 43 | let data: Vec = rlp.val_at(6)?; 44 | let access_list = rlp.list_at(7)?; 45 | 46 | let mut item = Self { 47 | chain_id, 48 | nonce, 49 | gas_price, 50 | gas_limit, 51 | to, 52 | value, 53 | data, 54 | access_list, 55 | sign: None, 56 | }; 57 | 58 | if item_count == 11 { 59 | let mut buf = BytesMut::new(); 60 | buf.extend_from_slice(&[1]); 61 | buf.extend_from_slice(rlp.as_raw()); 62 | 63 | let signature = Signature::create(&item, rlp, &buf, 8) 64 | .map_err(|_| rlp::DecoderError::Custom("Error while recovering signature"))?; 65 | 66 | item.sign = Some(signature); 67 | } 68 | 69 | Ok(item) 70 | } 71 | } 72 | 73 | impl Signable for Transaction2930 { 74 | fn get_bytes(&self, for_signing: bool) -> bytes::BytesMut { 75 | let mut rlp = RlpStream::new(); 76 | self.encode_rlp(&mut rlp, for_signing); 77 | 78 | let mut buf: BytesMut = BytesMut::new(); 79 | buf.put_u8(1u8); //write EIP2930 identifier 80 | buf.extend_from_slice(rlp.out().as_ref()); 81 | 82 | buf 83 | } 84 | 85 | fn encode_rlp(&self, rlp: &mut RlpStream, for_signing: bool) { 86 | rlp.begin_unbounded_list(); 87 | 88 | rlp.append(&self.chain_id); 89 | rlp.append(&self.nonce); 90 | rlp.append(&self.gas_price); 91 | rlp.append(&self.gas_limit); 92 | rlp.append(&self.to); 93 | rlp.append(&self.value); 94 | rlp.append(&self.data); 95 | rlp.append_list(&self.access_list); 96 | 97 | if !for_signing && self.sign.is_some() { 98 | if let Some(sign) = self.sign.as_ref() { 99 | rlp.append(&sign.v); 100 | rlp.append(&sign.r); 101 | rlp.append(&sign.s); 102 | } 103 | } 104 | 105 | rlp.finalize_unbounded_list(); 106 | } 107 | } 108 | 109 | #[cfg(test)] 110 | mod test { 111 | use crate::types::transaction::Transaction; 112 | use std::error::Error; 113 | 114 | #[test] 115 | fn decode_2930_transaction() -> Result<(), Box> { 116 | let data = "0x01f8ab010485032af8977482d91194bbbbca6a901c926f240b89eacb641d8aec7aeafd80b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc001a039c31c2e8693b56c4dc7b07fc77e1bfa6dd74f0cf55450f15a3c3e8ce0e2540ca071b92c8319f7415ba131e07f5481a20388bfd234640b58ae46c94ab2a5c2b7ea"; 117 | let data = hex::decode(data.trim_start_matches("0x"))?; 118 | 119 | let tx = Transaction::decode(&data)?; 120 | 121 | match tx { 122 | Transaction::EIP2930(x) => match x.sign { 123 | Some(sig) => { 124 | assert_eq!( 125 | "0xef9ae1e5329f145dfbc5a4c435601a1a22a41fd0", 126 | format!("{}", sig.from.unwrap()) 127 | ); 128 | assert_eq!( 129 | "0x50f38b90c94d32726648b36cacfafbf71a07e898fe1d1dbc692aa751cbf296cd", 130 | format!("{}", sig.hash) 131 | ); 132 | Ok(()) 133 | } 134 | None => panic!("Missing signature"), 135 | }, 136 | _ => panic!("Wrong transaction type"), 137 | } 138 | } 139 | 140 | #[test] 141 | fn encode_2930_transaction() -> Result<(), Box> { 142 | let data_hex = "0x01f8ab010485032af8977482d91194bbbbca6a901c926f240b89eacb641d8aec7aeafd80b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc001a039c31c2e8693b56c4dc7b07fc77e1bfa6dd74f0cf55450f15a3c3e8ce0e2540ca071b92c8319f7415ba131e07f5481a20388bfd234640b58ae46c94ab2a5c2b7ea"; 143 | let data = hex::decode(data_hex.trim_start_matches("0x"))?; 144 | 145 | let tx = Transaction::decode(&data)?; 146 | 147 | let encoded = tx.encode(false); 148 | let encoded_hex = format!("0x{}", hex::encode(&encoded)); 149 | 150 | assert_eq!(data, encoded); 151 | assert_eq!(data_hex, &encoded_hex); 152 | 153 | Ok(()) 154 | } 155 | 156 | #[test] 157 | fn encode_2930_access_list_transaction() -> Result<(), Box> { 158 | let data_hex = "0x01f906f701824c2085144790bc4f8304bdc394b9aa14b6774dbc9f84b15e7ff659c0dba0d9ddf5843b9aca00b8612e0000000000a0d4c2e5955530774b6a5dafaa45576c3938d779f9d7285aa45336bd2f2c9b79c7ddad45ed81791ce9abe0a0205b1e2773caa898ad7043ec4df2d22526ed331d54b6710000000000fc0f617ce7bc1300000011c497bae6763c10c2f90626f8dd94bd2f2c9b79c7ddad45ed81791ce9abe0a0205b1ef8c6a00000000000000000000000000000000000000000000000000000000000000102a000000000000000000000000000000000000000000000000000000000000000cca00000000000000000000000000000000000000000000000000000000000000100a000000000000000000000000000000000000000000000000000000000000000fea000000000000000000000000000000000000000000000000000000000000000ffa00000000000000000000000000000000000000000000000000000000000000101d6944a4efa663f98080227b0c8613976f985f2525d60c0f9026a942773caa898ad7043ec4df2d22526ed331d54b671f90252a0486cd66ca5810865f2eaa5f4fbb5d395a8e6d7e9dbebb5efbcb4bf93a23eb8f3a0d0fcde32b0215526075114b6a6af4706af88a4a1be3fa34388e6e5ac86c20d26a00000000000000000000000000000000000000000000000000000000000000008a0b1e5e7ab4ee56223f64c81cb8b785eb767023d97e06dac8879cf900afb990219a0d0fcde32b0215526075114b6a6af4706af88a4a1be3fa34388e6e5ac86c20d25a09c04773acff4c5c42718bd0120c72761f458e43068a3961eb935577d1ed4effba0b1e5e7ab4ee56223f64c81cb8b785eb767023d97e06dac8879cf900afb99021aa0b1e5e7ab4ee56223f64c81cb8b785eb767023d97e06dac8879cf900afb99021ba0b1e5e7ab4ee56223f64c81cb8b785eb767023d97e06dac8879cf900afb990218a0486cd66ca5810865f2eaa5f4fbb5d395a8e6d7e9dbebb5efbcb4bf93a23eb8f1a0486cd66ca5810865f2eaa5f4fbb5d395a8e6d7e9dbebb5efbcb4bf93a23eb8f2a0d0fcde32b0215526075114b6a6af4706af88a4a1be3fa34388e6e5ac86c20d24a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000004a0486cd66ca5810865f2eaa5f4fbb5d395a8e6d7e9dbebb5efbcb4bf93a23eb8f0a0d0fcde32b0215526075114b6a6af4706af88a4a1be3fa34388e6e5ac86c20d23a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002f87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0063467257e2af4c74a25e3e592134cf6a6796ea52a56b05c49fdbc0ea832b1dca0cc70ab56ed339e7c9e9bf51bcf50d1d180b2b10e715a53b3c643e78e6828d624a033df01d1414964c290c14fc9dc9e4ff05f7239b18ee44e6ddfbad504a04bdeb1f79423b0c2381075df4002bc6d3b9baf52ab0acb1e9be1a00000000000000000000000000000000000000000000000000000000000000001d69488c3e2ac77fcd790ffc2cbb0f10f20776851e2e2c0f8bc94774b6a5dafaa45576c3938d779f9d7285aa45336f8a5a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000002a09c04773acff4c5c42718bd0120c72761f458e43068a3961eb935577d1ed4effba00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000000f89b94881ba05de1e78f549cc63a8f6cabb1d4ad32250df884a0b55088d21d7a3d68b4dd0838035e491cf9632e8cc016506869e8803cb6fb31c6a00c23f089f2f76e2e5adec531a861a1a11cd50e704a930566c29856c414f10e25a0f1dda771943dc44f5c3a03fb3b53948058cc3c10d63d55a42fa0f2e7850ad18da0b4c0f9e4fcbeeab1458e94fe8faca61c369cde841e2dfbdb8914336724a9a639f89b947476d8b314607990957dda4479acf44ffa552034f884a0455ea71e93b0b19f9ed513d6f2b2a7c0a97189bb529df422b82a14b50eab6e84a00c23f089f2f76e2e5adec531a861a1a11cd50e704a930566c29856c414f10e25a00a867bf58e9a359b89c036573f9d6987985d133d090037fbb06c6d98683c45cea0f1dda771943dc44f5c3a03fb3b53948058cc3c10d63d55a42fa0f2e7850ad18d80a0c0fc871e2da1b57151046b77094bfc95cb953898965425a2f9acf8ac6f85d700a04589d36c9c07e7dbb5b494cd336d4d389be2a1689bc6f91a4a97db1ad5095d38"; 159 | let data = hex::decode(data_hex.trim_start_matches("0x"))?; 160 | 161 | let tx = Transaction::decode(&data)?; 162 | let encoded = tx.encode(false).to_vec(); 163 | let encoded_hex = format!("0x{}", hex::encode(&encoded)); 164 | 165 | assert_eq!(data, encoded); 166 | assert_eq!(data_hex, &encoded_hex); 167 | 168 | match tx { 169 | Transaction::EIP2930(x) => { 170 | assert_eq!(x.access_list.len(), 9); 171 | Ok(()) 172 | } 173 | _ => panic!("Wrong transaction type"), 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/evm_utils/src/types/transaction_legacy.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | use ic_cdk::export::candid::{CandidType, Deserialize}; 3 | use rlp::{Decodable, RlpStream}; 4 | 5 | use super::{ 6 | address::Address, 7 | num::U256, 8 | signature::{Signable, Signature}, 9 | }; 10 | 11 | #[derive(CandidType, Deserialize, Clone, PartialEq, Eq)] 12 | pub struct TransactionLegacy { 13 | pub chain_id: u64, 14 | pub nonce: U256, 15 | pub gas_price: U256, 16 | pub gas_limit: U256, 17 | pub to: Address, 18 | pub value: U256, 19 | pub data: Vec, 20 | pub sign: Option, 21 | } 22 | 23 | impl Decodable for TransactionLegacy { 24 | fn decode(rlp: &rlp::Rlp) -> Result { 25 | let item_count = rlp.item_count()?; 26 | if item_count != 6 && item_count != 9 { 27 | return Err(rlp::DecoderError::Custom( 28 | "Invalid parameters for legacy transaction", 29 | )); 30 | } 31 | 32 | let nonce: U256 = rlp.val_at(0)?; 33 | 34 | let gas_price: U256 = rlp.val_at(1)?; 35 | let gas_limit: U256 = rlp.val_at(2)?; 36 | 37 | let to = rlp.val_at(3)?; 38 | 39 | let value: U256 = rlp.val_at(4)?; 40 | let data: Vec = rlp.val_at(5)?; 41 | 42 | let mut item = Self { 43 | chain_id: 0, 44 | nonce, 45 | gas_price, 46 | gas_limit, 47 | to, 48 | value, 49 | data, 50 | sign: None, 51 | }; 52 | 53 | if item_count == 9 { 54 | let v: u64 = rlp.val_at(6)?; 55 | let r: Vec = rlp.val_at(7)?; 56 | 57 | if v >= 35 { 58 | item.chain_id = (v - 35) / 2; 59 | } else { 60 | item.chain_id = v; 61 | } 62 | 63 | if !r.is_empty() { 64 | let signature = Signature::create(&item, rlp, rlp.as_raw(), 6) 65 | .map_err(|_| rlp::DecoderError::Custom("Error while recovering signature"))?; 66 | 67 | item.sign = Some(signature); 68 | } 69 | } 70 | 71 | Ok(item) 72 | } 73 | } 74 | 75 | impl Signable for TransactionLegacy { 76 | fn get_bytes(&self, for_signing: bool) -> BytesMut { 77 | let mut rlp = RlpStream::new(); 78 | self.encode_rlp(&mut rlp, for_signing); 79 | 80 | let mut buf: BytesMut = BytesMut::new(); 81 | buf.extend_from_slice(rlp.out().as_ref()); 82 | 83 | buf 84 | } 85 | 86 | fn encode_rlp(&self, rlp: &mut RlpStream, for_signing: bool) { 87 | rlp.begin_unbounded_list(); 88 | 89 | rlp.append(&self.nonce); 90 | rlp.append(&self.gas_price); 91 | rlp.append(&self.gas_limit); 92 | rlp.append(&self.to); 93 | rlp.append(&self.value); 94 | rlp.append(&self.data); 95 | 96 | if !for_signing && self.sign.is_some() { 97 | if let Some(sign) = self.sign.as_ref() { 98 | rlp.append(&sign.v); 99 | rlp.append(&sign.r); 100 | rlp.append(&sign.s); 101 | } 102 | } else { 103 | rlp.append(&self.chain_id); 104 | rlp.append(&""); 105 | rlp.append(&""); 106 | } 107 | 108 | rlp.finalize_unbounded_list(); 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod test { 114 | use std::error::Error; 115 | 116 | use crate::types::transaction::Transaction; 117 | 118 | #[test] 119 | fn decode_legacy_no_signature_transaction() -> Result<(), Box> { 120 | let data = "e10182271082271094e94f1fa4f27d9d288ffea234bb62e1fbc086ca0c8080018080"; 121 | let data = hex::decode(data)?; 122 | 123 | let tx = Transaction::decode(&data)?; 124 | match tx { 125 | Transaction::Legacy(x) => { 126 | assert_eq!(x.chain_id, 1); 127 | Ok(()) 128 | } 129 | _ => panic!("Wrong transaction type"), 130 | } 131 | } 132 | 133 | #[test] 134 | fn decode_legacy_transaction() -> Result<(), Box> { 135 | let data = "f86e8302511e85036e1d083a826b6c948f2d10257ebf6386426456de1b1792b507426548875319b3e6ceb7bf8025a06716fc3c5bebebe88e61bc25714647b262904f7c99bd69c25541c7a796a9727fa071908b9fc3ce08f164cf1844ce43864a9347b7820a8921eef7aa67c55399e0be"; 136 | let data = hex::decode(data)?; 137 | 138 | let tx = Transaction::decode(&data)?; 139 | match tx { 140 | Transaction::Legacy(x) => { 141 | assert_eq!(x.chain_id, 1); 142 | match x.sign { 143 | Some(sig) => { 144 | assert_eq!( 145 | "0x690b9a9e9aa1c9db991c7721a92d351db4fac990", 146 | format!("{}", sig.from.unwrap()) 147 | ); 148 | assert_eq!( 149 | "0xd103e725e13c9886eb787517e47647010d077b51bc3a0a8b7ae7fc5a9cf351e2", 150 | format!("{}", sig.hash) 151 | ); 152 | Ok(()) 153 | } 154 | None => panic!("Missing signature"), 155 | } 156 | } 157 | _ => panic!("Wrong transaction type"), 158 | } 159 | } 160 | 161 | #[test] 162 | fn encode_legacy_transaction() -> Result<(), Box> { 163 | let data_hex = "f86e8302511e85036e1d083a826b6c948f2d10257ebf6386426456de1b1792b507426548875319b3e6ceb7bf8025a06716fc3c5bebebe88e61bc25714647b262904f7c99bd69c25541c7a796a9727fa071908b9fc3ce08f164cf1844ce43864a9347b7820a8921eef7aa67c55399e0be"; 164 | let data = hex::decode(data_hex)?; 165 | 166 | let tx = Transaction::decode(&data)?; 167 | 168 | let encoded = tx.encode(false); 169 | let encoded_hex = hex::encode(&encoded); 170 | 171 | assert_eq!(data, encoded); 172 | assert_eq!(data_hex, &encoded_hex); 173 | 174 | Ok(()) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/evm_utils/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use candid::candid_method; 4 | use ic_cdk::query; 5 | use secp256k1::ecdsa::RecoverableSignature; 6 | use secp256k1::ecdsa::RecoveryId; 7 | use secp256k1::Message; 8 | use secp256k1::PublicKey; 9 | use sha3::Digest; 10 | use sha3::Keccak256; 11 | 12 | use crate::types::address::Address; 13 | use crate::types::num::U256; 14 | 15 | /// Convenience function for calculation of keccak256 hash 16 | pub fn keccak256(data: &[&[u8]]) -> U256 { 17 | let mut hasher = Keccak256::new(); 18 | for i in data { 19 | hasher.update(i); 20 | } 21 | 22 | U256::from(hasher.finalize().as_ref()) 23 | } 24 | 25 | /// Recovers public key of a message signer 26 | pub fn _recover_public_key( 27 | r: &[u8], 28 | s: &[u8], 29 | v: u64, 30 | msg: &[u8], 31 | ) -> Result> { 32 | let mut sign = [0u8; 64]; 33 | 34 | sign[..32].copy_from_slice(&r[..32]); 35 | sign[32..].copy_from_slice(&s[..32]); 36 | 37 | let mut rec_id = v; 38 | if rec_id > 1 { 39 | rec_id -= 37; 40 | } 41 | 42 | let rec_id = RecoveryId::from_i32(rec_id as i32)?; 43 | let rec_sig = RecoverableSignature::from_compact(&sign, rec_id)?; 44 | 45 | let hash = keccak256(&[msg]); 46 | let msg = Message::from_slice(&hash.0)?; 47 | 48 | let pub_k = rec_sig.recover(&msg)?; 49 | Ok(pub_k) 50 | } 51 | 52 | #[query] 53 | #[candid_method(query)] 54 | fn recover_public_key(signature: Vec, msg: Vec) -> Result, String> { 55 | if signature.len() != 65 { 56 | Err(String::from("Invalid signature length!, should be 65")) 57 | } else { 58 | let public_key = _recover_public_key( 59 | &signature[..32], 60 | &signature[32..64], 61 | signature[64] as u64, 62 | &msg, 63 | ) 64 | .map_err(|x| format!("Error while recovering public key {x}"))?; 65 | 66 | Ok(public_key.serialize_uncompressed().to_vec()) 67 | } 68 | } 69 | 70 | #[query] 71 | #[candid_method(query)] 72 | fn pub_to_address(public_key: Vec) -> Result, String> { 73 | let pub_k = PublicKey::from_slice(&public_key[..]) 74 | .map_err(|x| format!("Error while reading public key {x}"))?; 75 | 76 | let addr = Address::from(pub_k); 77 | 78 | Ok(addr.0.to_vec()) 79 | } 80 | 81 | #[query] 82 | #[candid_method(query)] 83 | fn is_valid_public(public_key: Vec) -> Result<(), String> { 84 | PublicKey::from_slice(&public_key[..]) 85 | .map_err(|x| format!("Error while reading public key {x}"))?; 86 | 87 | Ok(()) 88 | } 89 | 90 | #[query] 91 | #[candid_method(query)] 92 | fn is_valid_signature(signature: Vec) -> Result<(), String> { 93 | if signature.len() != 65 { 94 | Err(String::from("Invalid signature length!, should be 65")) 95 | } else { 96 | let mut rec_id = signature[64]; 97 | if rec_id > 1 { 98 | rec_id -= 37; 99 | } 100 | let rec_id = 101 | RecoveryId::from_i32(rec_id as i32).map_err(|x| format!("Recovery Id error {x}"))?; 102 | 103 | RecoverableSignature::from_compact(&signature[..64], rec_id) 104 | .map_err(|x| format!("Recoverable signature error {x}"))?; 105 | 106 | Ok(()) 107 | } 108 | } 109 | --------------------------------------------------------------------------------