This commit is contained in:
DaniPopes 2023-03-25 02:16:46 +00:00 committed by GitHub
commit 49ba38cead
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 723 additions and 1413 deletions

View File

@ -31,19 +31,19 @@ jobs:
- name: all features
flags: --workspace --all-features
include:
# not workspace because compiling examples fails for no reason in CI,
# not workspace because compiling examples fails for no reason in CI
- os: windows-latest
flags:
name: no default features
flags: -p ethers --no-default-features
flags: --no-default-features
- os: windows-latest
flags:
name: default features
flags: -p ethers
flags: ""
- os: windows-latest
flags:
name: all features
flags: -p ethers --all-features
flags: --all-features
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
@ -85,16 +85,17 @@ jobs:
- name: live tests
run: cargo test -p ethers-etherscan --test it
live-tests:
name: live tests
runs-on: ubuntu-latest
concurrency: live-tests-${{ github.head_ref || github.run_id }}
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: live tests
run: cargo test -p ethers --all-features
# TODO: Create a new `ethers-tests` crate in the workspace for live tests.
# live-tests:
# name: live tests
# runs-on: ubuntu-latest
# concurrency: live-tests-${{ github.head_ref || github.run_id }}
# steps:
# - uses: actions/checkout@v3
# - uses: dtolnay/rust-toolchain@stable
# - uses: Swatinem/rust-cache@v2
# - name: live tests
# run: cargo test -p ethers-tests --all-features
# TODO: [#2191](https://github.com/gakonst/ethers-rs/issues/2191)
# feature-checks:

255
Cargo.lock generated
View File

@ -77,9 +77,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anyhow"
version = "1.0.69"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
[[package]]
name = "arrayvec"
@ -98,13 +98,13 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.66"
version = "0.1.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc"
checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.9",
]
[[package]]
@ -138,7 +138,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -209,9 +209,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.0.1"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5dd14596c0e5b954530d0e6f1fd99b89c03e313aa2086e8da4303701a09e1cf"
checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1"
[[package]]
name = "bitvec"
@ -485,7 +485,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -651,15 +651,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
@ -821,7 +812,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
dependencies = [
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -850,9 +841,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.92"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72"
checksum = "a9c00419335c41018365ddf7e4d5f1c12ee3659ddcf3e01974650ba1de73d038"
dependencies = [
"cc",
"cxxbridge-flags",
@ -862,9 +853,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.92"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613"
checksum = "fb8307ad413a98fff033c8545ecf133e3257747b3bae935e7602aab8aa92d4ca"
dependencies = [
"cc",
"codespan-reporting",
@ -872,24 +863,24 @@ dependencies = [
"proc-macro2",
"quote",
"scratch",
"syn",
"syn 2.0.9",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.92"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97"
checksum = "edc52e2eb08915cb12596d29d55f0b5384f00d697a646dbd269b6ecb0fbd9d31"
[[package]]
name = "cxxbridge-macro"
version = "1.0.92"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56"
checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.9",
]
[[package]]
@ -932,7 +923,7 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -1063,9 +1054,9 @@ dependencies = [
[[package]]
name = "ena"
version = "0.14.1"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2e5d13ca2353ab7d0230988629def93914a8c4015f621f9b13ed2955614731d"
checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1"
dependencies = [
"log",
]
@ -1240,8 +1231,8 @@ dependencies = [
"ethers-contract-abigen",
"ethers-contract-derive",
"ethers-core",
"ethers-derive-eip712",
"ethers-providers",
"ethers-signers",
"ethers-solc",
"futures-util",
"hex",
@ -1272,7 +1263,7 @@ dependencies = [
"reqwest",
"serde",
"serde_json",
"syn",
"syn 2.0.9",
"tempfile",
"tokio",
"toml",
@ -1284,12 +1275,14 @@ dependencies = [
name = "ethers-contract-derive"
version = "2.0.1"
dependencies = [
"Inflector",
"ethers-contract-abigen",
"ethers-core",
"hex",
"proc-macro2",
"quote",
"syn",
"serde_json",
"syn 2.0.9",
]
[[package]]
@ -1301,7 +1294,6 @@ dependencies = [
"bytes",
"cargo_metadata",
"chrono",
"convert_case",
"elliptic-curve",
"ethabi",
"generic-array",
@ -1312,31 +1304,18 @@ dependencies = [
"num_enum",
"once_cell",
"open-fastrlp",
"proc-macro2",
"rand",
"rlp",
"serde",
"serde_json",
"strum",
"syn",
"syn 2.0.9",
"tempfile",
"thiserror",
"tiny-keccak",
"unicode-xid",
]
[[package]]
name = "ethers-derive-eip712"
version = "2.0.1"
dependencies = [
"ethers-contract-derive",
"ethers-core",
"hex",
"quote",
"serde_json",
"syn",
]
[[package]]
name = "ethers-etherscan"
version = "2.0.1"
@ -1436,7 +1415,6 @@ dependencies = [
"eth-keystore",
"ethers-contract-derive",
"ethers-core",
"ethers-derive-eip712",
"futures-executor",
"futures-util",
"hex",
@ -1817,7 +1795,7 @@ checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -2138,16 +2116,16 @@ dependencies = [
[[package]]
name = "iana-time-zone"
version = "0.1.53"
version = "0.1.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"winapi",
"windows",
]
[[package]]
@ -2205,7 +2183,7 @@ checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -2216,9 +2194,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "indexmap"
version = "1.9.2"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown",
@ -2257,10 +2235,11 @@ dependencies = [
[[package]]
name = "io-lifetimes"
version = "1.0.6"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb"
dependencies = [
"hermit-abi 0.3.1",
"libc",
"windows-sys 0.45.0",
]
@ -2273,9 +2252,9 @@ checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
[[package]]
name = "is-terminal"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857"
checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e"
dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
@ -2496,9 +2475,9 @@ dependencies = [
[[package]]
name = "mime"
version = "0.3.16"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "miniz_oxide"
@ -2616,7 +2595,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -2665,14 +2644,14 @@ dependencies = [
"bytes",
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
name = "openssl"
version = "0.10.46"
version = "0.10.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd2523381e46256e40930512c7fd25562b9eae4812cb52078f155e87217c9d1e"
checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
@ -2691,7 +2670,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -2702,9 +2681,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.81"
version = "0.9.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176be2629957c157240f68f61f2d0053ad3a4ecfdd9ebf1e6521d18d9635cf67"
checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b"
dependencies = [
"autocfg",
"cc",
@ -2715,9 +2694,9 @@ dependencies = [
[[package]]
name = "os_str_bytes"
version = "6.4.1"
version = "6.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
[[package]]
name = "output_vt100"
@ -2791,7 +2770,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -2912,7 +2891,7 @@ dependencies = [
"phf_shared 0.11.1",
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -2956,7 +2935,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -3047,12 +3026,12 @@ dependencies = [
[[package]]
name = "prettyplease"
version = "0.1.25"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86"
checksum = "c17f5e28f5d12fb805aea10d72d50d2d9b005b267e71c44d0a33a847e45ed795"
dependencies = [
"proc-macro2",
"syn",
"syn 2.0.9",
]
[[package]]
@ -3097,7 +3076,7 @@ dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
"version_check",
]
@ -3114,9 +3093,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.52"
version = "1.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73"
dependencies = [
"unicode-ident",
]
@ -3222,9 +3201,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.7.1"
version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
dependencies = [
"aho-corasick",
"memchr",
@ -3242,15 +3221,15 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.28"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "reqwest"
version = "0.11.14"
version = "0.11.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9"
checksum = "0ba30cc2c0cd02af1222ed216ba659cdb2f879dfe3181852fe7c50b1d0005949"
dependencies = [
"base64 0.21.0",
"bytes",
@ -3341,7 +3320,7 @@ checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -3454,9 +3433,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.36.9"
version = "0.36.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc"
checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e"
dependencies = [
"bitflags 1.3.2",
"errno",
@ -3531,9 +3510,9 @@ dependencies = [
[[package]]
name = "scale-info"
version = "2.3.1"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608"
checksum = "61471dff9096de1d8b2319efed7162081e96793f5ebb147e50db10d50d648a4d"
dependencies = [
"cfg-if",
"derive_more",
@ -3543,14 +3522,14 @@ dependencies = [
[[package]]
name = "scale-info-derive"
version = "2.3.1"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c"
checksum = "219580e803a66b3f05761fd06f1f879a872444e49ce23f73694d26e5a954c7e6"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -3662,9 +3641,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
[[package]]
name = "serde"
version = "1.0.156"
version = "1.0.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4"
checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9"
dependencies = [
"serde_derive",
]
@ -3682,13 +3661,13 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.156"
version = "1.0.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d"
checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.9",
]
[[package]]
@ -3704,9 +3683,9 @@ dependencies = [
[[package]]
name = "serde_path_to_error"
version = "0.1.10"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db0969fff533976baadd92e08b1d102c5a3d8a8049eadfd69d4d1e3c5b2ed189"
checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0"
dependencies = [
"serde",
]
@ -3754,7 +3733,7 @@ checksum = "079a83df15f85d89a68d64ae1238f142f172b1fa915d0d76b26a7cba1b659a69"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -3845,7 +3824,7 @@ checksum = "ede930749cca4e3a3df7e37b5f0934a55693e01d028d7a4e506b44cbc059d95a"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -3952,7 +3931,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn",
"syn 1.0.109",
]
[[package]]
@ -4016,6 +3995,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0da4a3c17e109f700685ec577c0f85efd9b19bcf15c913985f14dc1ac01775aa"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.12.6"
@ -4024,7 +4014,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
"unicode-xid",
]
@ -4085,22 +4075,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]]
name = "thiserror"
version = "1.0.39"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.39"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.9",
]
[[package]]
@ -4201,7 +4191,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -4280,9 +4270,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.19.7"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274"
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
dependencies = [
"indexmap",
"serde",
@ -4317,7 +4307,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -4389,7 +4379,7 @@ checksum = "258bc1c4f8e2e73a977812ab339d503e6feeb92700f6d07a6de4d321522d5c08"
dependencies = [
"lazy_static",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@ -4455,9 +4445,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.11"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c"
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]]
name = "unicode-ident"
@ -4474,12 +4464,6 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.10"
@ -4601,7 +4585,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
"wasm-bindgen-shared",
]
@ -4635,7 +4619,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -4730,6 +4714,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.42.0"
@ -4813,9 +4806,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "winnow"
version = "0.3.6"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966"
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
dependencies = [
"memchr",
]
@ -4876,7 +4869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6a89568917376ff46a3de7bd0abdac47e9cc8ded4e1018e4a36d071d43a54ad"
dependencies = [
"aes 0.8.2",
"bitflags 2.0.1",
"bitflags 2.0.2",
"cbc",
"ccm",
"cmac",
@ -4920,7 +4913,7 @@ checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
"synstructure",
]

View File

@ -76,7 +76,6 @@ ethers-solc = { version = "2.0.1", path = "ethers-solc", default-features = fals
ethers-contract-abigen = { version = "2.0.1", path = "ethers-contract/ethers-contract-abigen", default-features = false }
ethers-contract-derive = { version = "2.0.1", path = "ethers-contract/ethers-contract-derive", default-features = false }
ethers-derive-eip712 = { version = "2.0.1", path = "ethers-core/ethers-derive-eip712", default-features = false }
# async / async utils
tokio = "1.26"
@ -107,11 +106,12 @@ serde_json = "1.0"
# macros
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0", features = ["extra-traits"] }
syn = { version = "2.0", features = ["extra-traits"] }
async-trait = "0.1.66"
auto_impl = "1.0"
# misc
Inflector = "0.11"
thiserror = "1.0"
once_cell = "1.17"
hex = "0.4"

View File

@ -39,10 +39,8 @@ hex.workspace = true
ethers-contract-abigen = { workspace = true, optional = true }
ethers-contract-derive = { workspace = true, optional = true }
# eip712
ethers-derive-eip712 = { workspace = true, optional = true }
[dev-dependencies]
ethers-signers.workspace = true
ethers-solc.workspace = true
ethers-providers = { workspace = true, features = ["ws"] }
@ -52,8 +50,6 @@ tokio = { workspace = true, features = ["macros"] }
[features]
default = ["abigen"]
eip712 = ["ethers-derive-eip712", "ethers-core/eip712"]
abigen-offline = ["ethers-contract-abigen", "ethers-contract-derive"]
abigen = ["abigen-offline", "ethers-contract-abigen/online"]

View File

@ -30,10 +30,10 @@ ethers-core = { workspace = true, features = ["macros"] }
proc-macro2.workspace = true
quote.workspace = true
syn.workspace = true
prettyplease = "0.1.25"
syn = { workspace = true, features = ["full"] }
prettyplease = "0.2.1"
Inflector = "0.11"
Inflector.workspace = true
serde.workspace = true
serde_json.workspace = true
hex.workspace = true

View File

@ -28,4 +28,6 @@ proc-macro2.workspace = true
quote.workspace = true
syn.workspace = true
Inflector.workspace = true
hex.workspace = true
serde_json.workspace = true

View File

@ -40,8 +40,7 @@ impl Contracts {
impl Parse for Contracts {
fn parse(input: ParseStream) -> Result<Self> {
let inner =
input.parse_terminated::<_, Token![;]>(ContractArgs::parse)?.into_iter().collect();
let inner = input.parse_terminated(ContractArgs::parse, Token![;])?.into_iter().collect();
Ok(Self { inner })
}
}
@ -122,7 +121,7 @@ impl Parse for Parameter {
"methods" => {
let content;
braced!(content in input);
let parsed = content.parse_terminated::<_, Token![;]>(Spanned::<Method>::parse)?;
let parsed = content.parse_terminated(Spanned::<Method>::parse, Token![;])?;
let mut methods = Vec::with_capacity(parsed.len());
let mut signatures = HashSet::new();
@ -141,7 +140,7 @@ impl Parse for Parameter {
"derives" | "event_derives" => {
let content;
parenthesized!(content in input);
let derives = content.parse_terminated::<_, Token![,]>(Path::parse)?;
let derives = content.parse_terminated(Path::parse, Token![,])?;
Ok(Parameter::Derives(derives))
}
_ => Err(Error::new(name.span(), "unexpected named parameter")),
@ -168,7 +167,7 @@ impl Parse for Method {
// function params
let content;
parenthesized!(content in input);
let params = content.parse_terminated::<_, Token![,]>(Ident::parse)?;
let params = content.parse_terminated(Ident::parse, Token![,])?;
let last_i = params.len().saturating_sub(1);
signature.push('(');

View File

@ -11,12 +11,10 @@ use syn::{parse::Error, DeriveInput};
/// Generates the `ethcall` trait support
pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> Result<TokenStream, Error> {
let attributes = parse_calllike_attributes(&input, "ethcall")?;
let attributes = parse_calllike_attributes!(input, "ethcall");
let function_call_name =
attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string());
let mut function = if let Some((abi, span)) = attributes.abi {
let function_call_name = attributes.name(&input.ident);
let mut function = if let Some((abi, span)) = attributes.abi() {
let sig = abi.trim_start_matches("function ").trim_start();
// try to parse as solidity function
match HumanReadableParser::parse_function(&abi) {

View File

@ -7,97 +7,50 @@ use ethers_core::{
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{parse::Error, spanned::Spanned, AttrStyle, DeriveInput, Lit, Meta, NestedMeta};
use syn::{DeriveInput, LitStr, Result};
/// All the attributes the `EthCall`/`EthError` macro supports
#[derive(Default)]
pub struct EthCalllikeAttributes {
pub name: Option<(String, Span)>,
pub abi: Option<(String, Span)>,
pub name: Option<LitStr>,
pub abi: Option<LitStr>,
}
/// extracts the attributes from the struct annotated with the given attribute
pub fn parse_calllike_attributes(
input: &DeriveInput,
attr_name: &str,
) -> Result<EthCalllikeAttributes, Error> {
let mut result = EthCalllikeAttributes::default();
for a in input.attrs.iter() {
if let AttrStyle::Outer = a.style {
if let Ok(Meta::List(meta)) = a.parse_meta() {
if meta.path.is_ident(attr_name) {
for n in meta.nested.iter() {
if let NestedMeta::Meta(meta) = n {
match meta {
Meta::Path(path) => {
return Err(Error::new(
path.span(),
format!("unrecognized {attr_name} parameter"),
))
}
Meta::List(meta) => {
return Err(Error::new(
meta.path.span(),
format!("unrecognized {attr_name} parameter"),
))
}
Meta::NameValue(meta) => {
if meta.path.is_ident("name") {
if let Lit::Str(ref lit_str) = meta.lit {
if result.name.is_none() {
result.name =
Some((lit_str.value(), lit_str.span()));
} else {
return Err(Error::new(
meta.span(),
"name already specified",
))
}
} else {
return Err(Error::new(
meta.span(),
"name must be a string",
))
}
} else if meta.path.is_ident("abi") {
if let Lit::Str(ref lit_str) = meta.lit {
if result.abi.is_none() {
result.abi =
Some((lit_str.value(), lit_str.span()));
} else {
return Err(Error::new(
meta.span(),
"abi already specified",
))
}
} else {
return Err(Error::new(
meta.span(),
"abi must be a string",
))
}
} else {
return Err(Error::new(
meta.span(),
format!("unrecognized {attr_name} parameter"),
))
}
}
}
}
}
}
}
}
impl EthCalllikeAttributes {
pub fn name(&self, fallback: &Ident) -> String {
self.name.as_ref().map(|s| s.value()).unwrap_or_else(|| fallback.to_string())
}
pub fn abi(&self) -> Option<(String, Span)> {
self.abi.as_ref().map(|s| (s.value(), s.span()))
}
Ok(result)
}
macro_rules! parse_calllike_attributes {
($input:ident, $attr_ident:literal) => {{
let mut result = EthCalllikeAttributes::default();
$crate::utils::parse_attributes!($input.attrs.iter(), $attr_ident, meta,
"name", result.name => {
meta.input.parse::<::syn::Token![=]>()?;
let litstr: ::syn::LitStr = meta.input.parse()?;
result.name = Some(litstr);
}
"abi", result.abi => {
meta.input.parse::<::syn::Token![=]>()?;
let litstr: ::syn::LitStr = meta.input.parse()?;
result.abi = Some(litstr);
}
);
result
}};
}
pub(crate) use parse_calllike_attributes;
/// Generates the decode implementation based on the type's runtime `AbiType` impl
pub fn derive_decode_impl_with_abi_type(
input: &DeriveInput,
trait_ident: Ident,
) -> Result<TokenStream, Error> {
) -> Result<TokenStream> {
let datatypes_array = utils::abi_parameters_array(input, &trait_ident.to_string())?;
Ok(derive_decode_impl(datatypes_array, trait_ident))
}
@ -130,7 +83,7 @@ pub fn derive_codec_impls(
input: &DeriveInput,
decode_impl: TokenStream,
trait_ident: Ident,
) -> Result<TokenStream, Error> {
) -> Result<TokenStream> {
// the ethers crates to use
let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();

View File

@ -0,0 +1,181 @@
use crate::utils;
use ethers_core::{
abi::{Address, ParamType},
macros::ethers_core_crate,
types::transaction::eip712::EIP712Domain,
utils::keccak256,
};
use inflector::Inflector;
use proc_macro2::{Literal, TokenStream};
use quote::quote;
use syn::{spanned::Spanned, Data, DeriveInput, Error, Fields, LitInt, LitStr, Result, Token};
pub(crate) fn impl_derive_eip712(input: &DeriveInput) -> Result<TokenStream> {
// Primary type should match the type in the ethereum verifying contract;
let primary_type = &input.ident;
// Instantiate domain from parsed attributes
let domain = parse_attributes(input)?;
let domain_separator = into_tokens(domain.separator());
let domain_str = serde_json::to_string(&domain).unwrap();
// Must parse the AST at compile time.
let parsed_fields = parse_fields(input)?;
// Compute the type hash for the derived struct using the parsed fields from above.
let type_hash = into_tokens(make_type_hash(primary_type.to_string(), &parsed_fields));
// Use reference to ethers_core instead of directly using the crate itself.
let ethers_core = ethers_core_crate();
let tokens = quote! {
impl #ethers_core::types::transaction::eip712::Eip712 for #primary_type {
type Error = #ethers_core::types::transaction::eip712::Eip712Error;
#[inline]
fn type_hash() -> ::core::result::Result<[u8; 32], Self::Error> {
Ok([#(#type_hash),*])
}
#[inline]
fn domain_separator(&self) -> ::core::result::Result<[u8; 32], Self::Error> {
Ok([#(#domain_separator),*])
}
fn domain(&self) -> ::core::result::Result<#ethers_core::types::transaction::eip712::EIP712Domain, Self::Error> {
#ethers_core::utils::__serde_json::from_str(#domain_str).map_err(::core::convert::Into::into)
}
fn struct_hash(&self) -> ::core::result::Result<[u8; 32], Self::Error> {
let mut items = vec![#ethers_core::abi::Token::Uint(
#ethers_core::types::U256::from(&Self::type_hash()?[..]),
)];
if let #ethers_core::abi::Token::Tuple(tokens) =
#ethers_core::abi::Tokenizable::into_token(::core::clone::Clone::clone(self))
{
items.reserve(tokens.len());
for token in tokens {
match &token {
#ethers_core::abi::Token::Tuple(t) => {
// TODO: check for nested Eip712 Type;
// Challenge is determining the type hash
return Err(Self::Error::NestedEip712StructNotImplemented);
},
_ => {
items.push(#ethers_core::types::transaction::eip712::encode_eip712_type(token));
}
}
}
}
let struct_hash = #ethers_core::utils::keccak256(#ethers_core::abi::encode(
&items,
));
Ok(struct_hash)
}
}
};
Ok(tokens)
}
fn parse_attributes(input: &DeriveInput) -> Result<EIP712Domain> {
let mut domain = EIP712Domain::default();
utils::parse_attributes!(input.attrs.iter(), "eip712", meta,
"name", domain.name => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
domain.name = Some(litstr.value());
}
"version", domain.version => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
domain.version = Some(litstr.value());
}
"chain_id", domain.chain_id => {
meta.input.parse::<Token![=]>()?;
let litint: LitInt = meta.input.parse()?;
let n: u64 = litint.base10_parse()?;
domain.chain_id = Some(n.into());
}
"verifying_contract", domain.verifying_contract => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
let addr: Address =
litstr.value().parse().map_err(|e| Error::new(litstr.span(), e))?;
domain.verifying_contract = Some(addr);
}
"salt", domain.salt => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
let hash = keccak256(litstr.value());
domain.salt = Some(hash);
}
);
Ok(domain)
}
/// Returns a Vec of `(name, param_type)`
fn parse_fields(input: &DeriveInput) -> Result<Vec<(String, ParamType)>> {
let data = match &input.data {
Data::Struct(s) => s,
Data::Enum(e) => {
return Err(Error::new(e.enum_token.span, "Eip712 is not derivable for enums"))
}
Data::Union(u) => {
return Err(Error::new(u.union_token.span, "Eip712 is not derivable for unions"))
}
};
let named_fields = match &data.fields {
Fields::Named(fields) => fields,
_ => return Err(Error::new(input.span(), "unnamed fields are not supported")),
};
let mut fields = Vec::with_capacity(named_fields.named.len());
for f in named_fields.named.iter() {
let field_name = f.ident.as_ref().unwrap().to_string().to_camel_case();
let field_type =
match f.attrs.iter().find(|a| a.path().segments.iter().any(|s| s.ident == "eip712")) {
// Found nested Eip712 Struct
// TODO: Implement custom
Some(a) => {
return Err(Error::new(a.span(), "nested Eip712 struct are not yet supported"))
}
// Not a nested eip712 struct, return the field param type;
None => crate::utils::find_parameter_type(&f.ty)?,
};
fields.push((field_name, field_type));
}
Ok(fields)
}
/// Convert hash map of field names and types into a type hash corresponding to enc types;
fn make_type_hash(primary_type: String, fields: &[(String, ParamType)]) -> [u8; 32] {
let mut sig = String::with_capacity(256);
sig.push_str(&primary_type);
sig.push('(');
for (i, (name, ty)) in fields.iter().enumerate() {
sig.push_str(&ty.to_string());
sig.push(' ');
sig.push_str(name);
if i < fields.len() - 1 {
sig.push(',');
}
}
sig.push(')');
keccak256(sig)
}
fn into_tokens(bytes: [u8; 32]) -> impl Iterator<Item = Literal> {
bytes.into_iter().map(Literal::u8_suffixed)
}

View File

@ -11,11 +11,10 @@ use syn::{parse::Error, DeriveInput};
/// Generates the `EthError` trait support
pub(crate) fn derive_eth_error_impl(input: DeriveInput) -> Result<TokenStream, Error> {
let attributes = parse_calllike_attributes(&input, "etherror")?;
let attributes = parse_calllike_attributes!(input, "etherror");
let error_name = attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string());
let mut error = if let Some((src, span)) = attributes.abi {
let error_name = attributes.name(&input.ident);
let mut error = if let Some((src, span)) = attributes.abi() {
let raw_function_sig = src.trim_start_matches("error ").trim_start();
// try to parse as solidity error
match HumanReadableParser::parse_error(&src) {

View File

@ -6,16 +6,12 @@ use ethers_core::{
abi::{Event, EventExt, EventParam, HumanReadableParser},
macros::{ethers_contract_crate, ethers_core_crate},
};
use hex::FromHex;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
parse::Error, spanned::Spanned, AttrStyle, Data, DeriveInput, Field, Fields, Lit, Meta,
NestedMeta,
};
use syn::{spanned::Spanned, Data, DeriveInput, Error, Field, Fields, LitStr, Result, Token};
/// Generates the `EthEvent` trait support
pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result<TokenStream, Error> {
pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result<TokenStream> {
let name = &input.ident;
let attributes = parse_event_attributes(&input)?;
@ -64,7 +60,7 @@ pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result<TokenStream, E
let (abi, event_sig) = (event.abi_signature(), event.signature());
let signature = if let Some((hash, _)) = attributes.signature_hash {
let signature = if let Some((hash, _)) = attributes.signature {
utils::signature(&hash)
} else {
utils::signature(event_sig.as_bytes())
@ -122,7 +118,7 @@ impl EventField {
}
}
fn derive_decode_from_log_impl(input: &DeriveInput, event: &Event) -> Result<TokenStream, Error> {
fn derive_decode_from_log_impl(input: &DeriveInput, event: &Event) -> Result<TokenStream> {
let ethers_core = ethers_core_crate();
let fields: Vec<_> = match input.data {
@ -291,7 +287,7 @@ fn derive_decode_from_log_impl(input: &DeriveInput, event: &Event) -> Result<Tok
}
/// Determine the event's ABI by parsing the AST
fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event, Error> {
fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event> {
let event = Event {
name: input.ident.to_string(),
inputs: utils::derive_abi_inputs_from_fields(input, "EthEvent")?
@ -303,53 +299,18 @@ fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event, Error> {
Ok(event)
}
fn parse_field_attributes(field: &Field) -> Result<(Option<String>, bool), Error> {
let mut indexed = false;
let mut topic_name = None;
for a in field.attrs.iter() {
if let AttrStyle::Outer = a.style {
if let Ok(Meta::List(meta)) = a.parse_meta() {
if meta.path.is_ident("ethevent") {
for n in meta.nested.iter() {
if let NestedMeta::Meta(meta) = n {
match meta {
Meta::Path(path) => {
if path.is_ident("indexed") {
indexed = true;
} else {
return Err(Error::new(
path.span(),
"unrecognized ethevent parameter",
))
}
}
Meta::List(meta) => {
return Err(Error::new(
meta.path.span(),
"unrecognized ethevent parameter",
))
}
Meta::NameValue(meta) => {
if meta.path.is_ident("name") {
if let Lit::Str(ref lit_str) = meta.lit {
topic_name = Some(lit_str.value());
} else {
return Err(Error::new(
meta.span(),
"name attribute must be a string",
))
}
}
}
}
}
}
}
}
fn parse_field_attributes(field: &Field) -> Result<(Option<String>, bool)> {
let mut indexed = None::<bool>;
let mut topic_name = None::<String>;
utils::parse_attributes!(field.attrs.iter(), "ethevent", meta,
"indexed", indexed => { indexed = Some(true) }
"name", topic_name => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
topic_name = Some(litstr.value());
}
}
Ok((topic_name, indexed))
);
Ok((topic_name, indexed.unwrap_or_default()))
}
/// All the attributes the `EthEvent` macro supports
@ -357,139 +318,32 @@ fn parse_field_attributes(field: &Field) -> Result<(Option<String>, bool), Error
struct EthEventAttributes {
name: Option<(String, Span)>,
abi: Option<(String, Span)>,
signature_hash: Option<(Vec<u8>, Span)>,
signature: Option<(Vec<u8>, Span)>,
anonymous: Option<(bool, Span)>,
}
/// extracts the attributes from the struct annotated with `EthEvent`
fn parse_event_attributes(input: &DeriveInput) -> Result<EthEventAttributes, Error> {
fn parse_event_attributes(input: &DeriveInput) -> Result<EthEventAttributes> {
let mut result = EthEventAttributes::default();
for a in input.attrs.iter() {
if let AttrStyle::Outer = a.style {
if let Ok(Meta::List(meta)) = a.parse_meta() {
if meta.path.is_ident("ethevent") {
for n in meta.nested.iter() {
if let NestedMeta::Meta(meta) = n {
match meta {
Meta::Path(path) => {
if let Some(name) = path.get_ident() {
if &*name.to_string() == "anonymous" {
if result.anonymous.is_none() {
result.anonymous = Some((true, name.span()));
continue
} else {
return Err(Error::new(
name.span(),
"anonymous already specified",
))
}
}
}
return Err(Error::new(
path.span(),
"unrecognized ethevent parameter",
))
}
Meta::List(meta) => {
return Err(Error::new(
meta.path.span(),
"unrecognized ethevent parameter",
))
}
Meta::NameValue(meta) => {
if meta.path.is_ident("anonymous") {
if let Lit::Bool(ref bool_lit) = meta.lit {
if result.anonymous.is_none() {
result.anonymous =
Some((bool_lit.value, bool_lit.span()));
} else {
return Err(Error::new(
meta.span(),
"anonymous already specified",
))
}
} else {
return Err(Error::new(
meta.span(),
"name must be a string",
))
}
} else if meta.path.is_ident("name") {
if let Lit::Str(ref lit_str) = meta.lit {
if result.name.is_none() {
result.name =
Some((lit_str.value(), lit_str.span()));
} else {
return Err(Error::new(
meta.span(),
"name already specified",
))
}
} else {
return Err(Error::new(
meta.span(),
"name must be a string",
))
}
} else if meta.path.is_ident("abi") {
if let Lit::Str(ref lit_str) = meta.lit {
if result.abi.is_none() {
result.abi =
Some((lit_str.value(), lit_str.span()));
} else {
return Err(Error::new(
meta.span(),
"abi already specified",
))
}
} else {
return Err(Error::new(
meta.span(),
"abi must be a string",
))
}
} else if meta.path.is_ident("signature") {
if let Lit::Str(ref lit_str) = meta.lit {
if result.signature_hash.is_none() {
match Vec::from_hex(lit_str.value()) {
Ok(sig) => {
result.signature_hash =
Some((sig, lit_str.span()))
}
Err(err) => {
return Err(Error::new(
meta.span(),
format!(
"Expected hex signature: {err:?}"
),
))
}
}
} else {
return Err(Error::new(
meta.span(),
"signature already specified",
))
}
} else {
return Err(Error::new(
meta.span(),
"signature must be a hex string",
))
}
} else {
return Err(Error::new(
meta.span(),
"unrecognized ethevent parameter",
))
}
}
}
}
}
}
}
utils::parse_attributes!(input.attrs.iter(), "ethevent", meta,
"name", result.name => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
result.name = Some((litstr.value(), litstr.span()));
}
}
"abi", result.abi => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
result.abi = Some((litstr.value(), litstr.span()));
}
"signature", result.signature => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
let s = litstr.value();
let b = hex::decode(s.strip_prefix("0x").unwrap_or(&s)).map_err(|e| meta.error(e))?;
result.signature = Some((b, litstr.span()));
}
"anonymous", result.anonymous => { result.anonymous = Some((true, meta.path.span())); }
);
Ok(result)
}

View File

@ -14,6 +14,7 @@ mod call;
pub(crate) mod calllike;
mod codec;
mod display;
mod eip712;
mod error;
mod event;
mod spanned;
@ -358,3 +359,75 @@ pub fn derive_abi_error(input: TokenStream) -> TokenStream {
}
.into()
}
/// EIP-712 derive macro.
///
/// This crate provides a derive macro `Eip712` that is used to encode a rust struct
/// into a payload hash, according to <https://eips.ethereum.org/EIPS/eip-712>
///
/// The trait used to derive the macro is found in `ethers_core::transaction::eip712::Eip712`
/// Both the derive macro and the trait must be in context when using
///
/// This derive macro requires the `#[eip712]` attributes to be included
/// for specifying the domain separator used in encoding the hash.
///
/// NOTE: In addition to deriving `Eip712` trait, the `EthAbiType` trait must also be derived.
/// This allows the struct to be parsed into `ethers_core::abi::Token` for encoding.
///
/// # Optional Eip712 Parameters
///
/// The only optional parameter is `salt`, which accepts a string
/// that is hashed using keccak256 and stored as bytes.
///
/// # Example Usage
///
/// ```ignore
/// use ethers_contract::EthAbiType;
/// use ethers_derive_eip712::*;
/// use ethers_core::types::{transaction::eip712::Eip712, H160};
///
/// #[derive(Debug, Eip712, EthAbiType)]
/// #[eip712(
/// name = "Radicle",
/// version = "1",
/// chain_id = 1,
/// verifying_contract = "0x0000000000000000000000000000000000000000"
/// // salt is an optional parameter
/// salt = "my-unique-spice"
/// )]
/// pub struct Puzzle {
/// pub organization: H160,
/// pub contributor: H160,
/// pub commit: String,
/// pub project: String,
/// }
///
/// let puzzle = Puzzle {
/// organization: "0000000000000000000000000000000000000000"
/// .parse::<H160>()
/// .expect("failed to parse address"),
/// contributor: "0000000000000000000000000000000000000000"
/// .parse::<H160>()
/// .expect("failed to parse address"),
/// commit: "5693b7019eb3e4487a81273c6f5e1832d77acb53".to_string(),
/// project: "radicle-reward".to_string(),
/// };
///
/// let hash = puzzle.encode_eip712().unwrap();
/// ```
///
/// # Limitations
///
/// At the moment, the derive macro does not recursively encode nested Eip712 structs.
///
/// There is an Inner helper attribute `#[eip712]` for fields that will eventually be used to
/// determine if there is a nested eip712 struct. However, this work is not yet complete.
#[proc_macro_derive(Eip712, attributes(eip712))]
pub fn derive_eip712(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match eip712::impl_derive_eip712(&input) {
Ok(tokens) => tokens,
Err(e) => e.to_compile_error(),
}
.into()
}

View File

@ -6,6 +6,35 @@ use syn::{
PathArguments, Type,
};
/// Parses the specified attributes from a `syn::Attribute` iterator.
macro_rules! parse_attributes {
($attrs:expr, $attr_ident:literal, $meta:ident, $($field:pat, $opt:expr => $block:block)*) => {
const ERROR: &str = concat!("unrecognized ", $attr_ident, " attribute");
const ALREADY_SPECIFIED: &str = concat!($attr_ident, " attribute already specified");
for attr in $attrs {
if !attr.path().is_ident($attr_ident) {
continue;
}
attr.parse_nested_meta(|$meta| {
let ident = $meta.path.get_ident().ok_or_else(|| $meta.error(ERROR))?.to_string();
match ident.as_str() {
$(
$field if $opt.is_none() => $block,
$field => return Err($meta.error(ALREADY_SPECIFIED)),
)*
_ => return Err($meta.error(ERROR)),
}
Ok(())
})?;
}
};
}
pub(crate) use parse_attributes;
pub fn ident(name: &str) -> Ident {
Ident::new(name, Span::call_site())
}
@ -104,17 +133,22 @@ pub fn param_type_quote(kind: &ParamType) -> TokenStream {
/// Tries to find the corresponding `ParamType` used for tokenization for the
/// given type
pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
const ERROR: &str = "Failed to derive proper ABI from array field";
match ty {
Type::Array(arr) => {
let ty = find_parameter_type(&arr.elem)?;
if let Expr::Lit(ref expr) = arr.len {
if let Lit::Int(ref len) = expr.lit {
if let Ok(size) = len.base10_parse::<usize>() {
return Ok(ParamType::FixedArray(Box::new(ty), size))
if let Ok(len) = len.base10_parse::<usize>() {
return match (ty, len) {
(ParamType::Uint(8), 32) => Ok(ParamType::FixedBytes(32)),
(ty, len) => Ok(ParamType::FixedArray(Box::new(ty), len)),
}
}
}
}
Err(Error::new(arr.span(), "Failed to derive proper ABI from array field"))
Err(Error::new(arr.span(), ERROR))
}
Type::Path(ty) => {
@ -151,7 +185,7 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
s => parse_param_type(s),
}
})
.ok_or_else(|| Error::new(ty.span(), "Failed to derive proper ABI from fields"))
.ok_or_else(|| Error::new(ty.span(), ERROR))
}
Type::Tuple(ty) => ty
@ -161,7 +195,7 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
.collect::<Result<Vec<_>, _>>()
.map(ParamType::Tuple),
_ => Err(Error::new(ty.span(), "Failed to derive proper ABI from fields")),
_ => Err(Error::new(ty.span(), ERROR)),
}
}

View File

@ -60,16 +60,13 @@ pub use ethers_contract_abigen::{
#[cfg(any(test, feature = "abigen"))]
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
pub use ethers_contract_derive::{
abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthError, EthEvent,
abigen, Eip712, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthError, EthEvent,
};
// Hide the Lazy re-export, it's just for convenience
#[doc(hidden)]
pub use once_cell::sync::Lazy;
#[cfg(feature = "eip712")]
pub use ethers_derive_eip712::*;
// For macro expansions only, not public API.
// See: [#2235](https://github.com/gakonst/ethers-rs/pull/2235)

View File

@ -1,14 +1,18 @@
use crate::common::*;
use ethers_contract::{
abigen, ContractFactory, ContractInstance, EthEvent, LogMeta, Multicall, MulticallError,
MulticallVersion,
abigen, ContractFactory, ContractInstance, Eip712, EthAbiType, EthEvent, LogMeta, Multicall,
MulticallError, MulticallVersion,
};
use ethers_core::{
abi::{encode, AbiEncode, Token, Tokenizable},
types::{Address, BlockId, Bytes, Filter, ValueOrArray, H160, H256, U256},
types::{
transaction::eip712::*, Address, BlockId, Bytes, Filter, ValueOrArray, H160, H256, I256,
U256,
},
utils::{keccak256, Anvil},
};
use ethers_providers::{Http, Middleware, MiddlewareError, Provider, StreamExt};
use ethers_providers::{Http, Middleware, MiddlewareError, Provider, StreamExt, Ws};
use ethers_signers::{LocalWallet, Signer};
use std::{sync::Arc, time::Duration};
#[derive(Debug)]
@ -337,7 +341,7 @@ async fn watch_events() {
let mut stream = event.stream().await.unwrap();
// Also set up a subscription for the same thing
let ws = Provider::connect(anvil.ws_endpoint()).await.unwrap();
let ws = Provider::<Ws>::connect(anvil.ws_endpoint()).await.unwrap();
let contract2 = ethers_contract::Contract::new(contract.address(), abi, ws.into());
let event2 = contract2.event::<ValueChanged>();
let mut subscription = event2.subscribe().await.unwrap();
@ -376,7 +380,7 @@ async fn watch_subscription_events_multiple_addresses() {
let contract_1 = deploy(client.clone(), abi.clone(), bytecode.clone()).await;
let contract_2 = deploy(client.clone(), abi.clone(), bytecode).await;
let ws = Provider::connect(anvil.ws_endpoint()).await.unwrap();
let ws = Provider::<Ws>::connect(anvil.ws_endpoint()).await.unwrap();
let filter = Filter::new()
.address(ValueOrArray::Array(vec![contract_1.address(), contract_2.address()]));
let mut stream = ws.subscribe_logs(&filter).await.unwrap();
@ -779,3 +783,129 @@ async fn multicall_aggregate() {
assert_eq!(bytes[..4], keccak256("CustomErrorWithData(string)")[..4]);
assert_eq!(bytes[4..], encode(&[Token::String("Data".to_string())]));
}
#[tokio::test]
async fn test_derive_eip712() {
// Generate Contract ABI Bindings
mod contract {
ethers_contract::abigen!(
DeriveEip712Test,
"./ethers-contract/tests/solidity-contracts/DeriveEip712Test.json",
derives(serde::Deserialize, serde::Serialize)
);
}
// Create derived structs
#[derive(Debug, Clone, Eip712, EthAbiType)]
#[eip712(
name = "Eip712Test",
version = "1",
chain_id = 1,
verifying_contract = "0x0000000000000000000000000000000000000001",
salt = "eip712-test-75F0CCte"
)]
struct FooBar {
foo: I256,
bar: U256,
fizz: Bytes,
buzz: [u8; 32],
far: String,
out: Address,
}
// launch the network & connect to it
let anvil = Anvil::new().spawn();
let wallet: LocalWallet = anvil.keys()[0].clone().into();
let provider = Provider::try_from(anvil.endpoint())
.unwrap()
.with_sender(wallet.address())
.interval(std::time::Duration::from_millis(10));
let client = Arc::new(provider);
let contract: contract::DeriveEip712Test<_> =
contract::DeriveEip712Test::deploy(client.clone(), ()).unwrap().send().await.unwrap();
let foo_bar = FooBar {
foo: I256::from(10u64),
bar: U256::from(20u64),
fizz: b"fizz".into(),
buzz: keccak256("buzz"),
far: String::from("space"),
out: Address::zero(),
};
let derived_foo_bar = contract::FooBar {
foo: foo_bar.foo,
bar: foo_bar.bar,
fizz: foo_bar.fizz.clone(),
buzz: foo_bar.buzz,
far: foo_bar.far.clone(),
out: foo_bar.out,
};
let sig = wallet.sign_typed_data(&foo_bar).await.expect("failed to sign typed data");
let mut r = [0; 32];
sig.r.to_big_endian(&mut r);
let mut s = [0; 32];
sig.s.to_big_endian(&mut s);
let v = sig.v as u8;
let domain_separator = contract
.domain_separator()
.call()
.await
.expect("failed to retrieve domain_separator from contract");
let type_hash =
contract.type_hash().call().await.expect("failed to retrieve type_hash from contract");
let struct_hash = contract
.struct_hash(derived_foo_bar.clone())
.call()
.await
.expect("failed to retrieve struct_hash from contract");
let encoded = contract
.encode_eip_712(derived_foo_bar.clone())
.call()
.await
.expect("failed to retrieve eip712 encoded hash from contract");
let verify = contract
.verify_foo_bar(wallet.address(), derived_foo_bar, r, s, v)
.call()
.await
.expect("failed to verify signed typed data eip712 payload");
assert_eq!(
domain_separator,
foo_bar
.domain()
.expect("failed to return domain_separator from Eip712 implemented struct")
.separator(),
"domain separator does not match contract domain separator!"
);
assert_eq!(
type_hash,
FooBar::type_hash().expect("failed to return type_hash from Eip712 implemented struct"),
"type hash does not match contract struct type hash!"
);
assert_eq!(
struct_hash,
foo_bar
.clone()
.struct_hash()
.expect("failed to return struct_hash from Eip712 implemented struct"),
"struct hash does not match contract struct hash!"
);
assert_eq!(
encoded,
foo_bar
.encode_eip712()
.expect("failed to return domain_separator from Eip712 implemented struct"),
"Encoded value does not match!"
);
assert!(verify, "typed data signature failed!");
}

View File

@ -1,6 +1,4 @@
#![allow(clippy::extra_unused_type_parameters)]
use ethers_contract_derive::EthAbiType;
use ethers_contract_derive::{Eip712, EthAbiType};
use ethers_core::{
types::{
transaction::eip712::{
@ -11,7 +9,6 @@ use ethers_core::{
},
utils::{keccak256, parse_ether},
};
use ethers_derive_eip712::*;
#[test]
fn test_derive_eip712() {

View File

@ -7,6 +7,8 @@ mod derive;
mod contract_call;
mod eip712;
#[cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
mod common;

File diff suppressed because one or more lines are too long

View File

@ -2,6 +2,7 @@
pragma solidity >=0.6.0;
pragma experimental ABIEncoderV2;
// note that this file is not synced with DeriveEip712Test.json
contract DeriveEip712Test {
uint256 constant chainId = 1;
bytes32 constant salt = keccak256("eip712-test-75F0CCte");

View File

@ -1,206 +0,0 @@
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "domainSeparator",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "int256",
"name": "foo",
"type": "int256"
},
{
"internalType": "uint256",
"name": "bar",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "fizz",
"type": "bytes"
},
{
"internalType": "bytes32",
"name": "buzz",
"type": "bytes32"
},
{
"internalType": "string",
"name": "far",
"type": "string"
},
{
"internalType": "address",
"name": "out",
"type": "address"
}
],
"internalType": "struct DeriveEip712Test.FooBar",
"name": "fooBar",
"type": "tuple"
}
],
"name": "encodeEip712",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "int256",
"name": "foo",
"type": "int256"
},
{
"internalType": "uint256",
"name": "bar",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "fizz",
"type": "bytes"
},
{
"internalType": "bytes32",
"name": "buzz",
"type": "bytes32"
},
{
"internalType": "string",
"name": "far",
"type": "string"
},
{
"internalType": "address",
"name": "out",
"type": "address"
}
],
"internalType": "struct DeriveEip712Test.FooBar",
"name": "fooBar",
"type": "tuple"
}
],
"name": "structHash",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "typeHash",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"components": [
{
"internalType": "int256",
"name": "foo",
"type": "int256"
},
{
"internalType": "uint256",
"name": "bar",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "fizz",
"type": "bytes"
},
{
"internalType": "bytes32",
"name": "buzz",
"type": "bytes32"
},
{
"internalType": "string",
"name": "far",
"type": "string"
},
{
"internalType": "address",
"name": "out",
"type": "address"
}
],
"internalType": "struct DeriveEip712Test.FooBar",
"name": "fooBar",
"type": "tuple"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
}
],
"name": "verifyFooBar",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "pure",
"type": "function"
}
]

View File

@ -51,11 +51,7 @@ num_enum = "0.5"
# macros feature enabled dependencies
cargo_metadata = { version = "0.15.3", optional = true }
# eip712 feature enabled dependencies
convert_case = { version = "0.6.0", optional = true }
syn = { workspace = true, optional = true }
proc-macro2 = { workspace = true, optional = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tempfile.workspace = true
@ -73,5 +69,4 @@ rand.workspace = true
[features]
celo = ["legacy"] # celo support extends the transaction format with extra fields
legacy = []
eip712 = ["convert_case", "syn", "proc-macro2"]
macros = ["syn", "cargo_metadata", "once_cell"]

View File

@ -1,29 +0,0 @@
[package]
name = "ethers-derive-eip712"
authors = ["Ryan Tate <ryan.michael.tate@gmail.com>"]
description = "Derive procedural macro for EIP-712 typed data"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
documentation.workspace = true
repository.workspace = true
homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[lib]
proc-macro = true
[dependencies]
ethers-core = { workspace = true, features = ["eip712", "macros"] }
quote.workspace = true
syn.workspace = true
hex.workspace = true
serde_json.workspace = true
[dev-dependencies]
ethers-contract-derive.workspace = true

View File

@ -1,172 +0,0 @@
//! # EIP-712 Derive Macro
//!
//! This crate provides a derive macro `Eip712` that is used to encode a rust struct
//! into a payload hash, according to [https://eips.ethereum.org/EIPS/eip-712](https://eips.ethereum.org/EIPS/eip-712)
//!
//! The trait used to derive the macro is found in `ethers_core::transaction::eip712::Eip712`
//! Both the derive macro and the trait must be in context when using
//!
//! This derive macro requires the `#[eip712]` attributes to be included
//! for specifying the domain separator used in encoding the hash.
//!
//! NOTE: In addition to deriving `Eip712` trait, the `EthAbiType` trait must also be derived.
//! This allows the struct to be parsed into `ethers_core::abi::Token` for encoding.
//!
//! # Optional Eip712 Parameters
//!
//! The only optional parameter is `salt`, which accepts a string
//! that is hashed using keccak256 and stored as bytes.
//!
//! # Example Usage
//!
//! ```ignore
//! use ethers_contract::EthAbiType;
//! use ethers_derive_eip712::*;
//! use ethers_core::types::{transaction::eip712::Eip712, H160};
//!
//! #[derive(Debug, Eip712, EthAbiType)]
//! #[eip712(
//! name = "Radicle",
//! version = "1",
//! chain_id = 1,
//! verifying_contract = "0x0000000000000000000000000000000000000000"
//! // salt is an optional parameter
//! salt = "my-unique-spice"
//! )]
//! pub struct Puzzle {
//! pub organization: H160,
//! pub contributor: H160,
//! pub commit: String,
//! pub project: String,
//! }
//!
//! let puzzle = Puzzle {
//! organization: "0000000000000000000000000000000000000000"
//! .parse::<H160>()
//! .expect("failed to parse address"),
//! contributor: "0000000000000000000000000000000000000000"
//! .parse::<H160>()
//! .expect("failed to parse address"),
//! commit: "5693b7019eb3e4487a81273c6f5e1832d77acb53".to_string(),
//! project: "radicle-reward".to_string(),
//! };
//!
//! let hash = puzzle.encode_eip712().unwrap();
//! ```
//!
//! # Limitations
//!
//! At the moment, the derive macro does not recursively encode nested Eip712 structs.
//!
//! There is an Inner helper attribute `#[eip712]` for fields that will eventually be used to
//! determine if there is a nested eip712 struct. However, this work is not yet complete.
#![deny(missing_docs, unsafe_code, rustdoc::broken_intra_doc_links)]
use ethers_core::{macros::ethers_core_crate, types::transaction::eip712};
use proc_macro::TokenStream;
use quote::quote;
use std::convert::TryFrom;
use syn::parse_macro_input;
/// Derive macro for `Eip712`
#[proc_macro_derive(Eip712, attributes(eip712))]
pub fn eip_712_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input);
impl_eip_712_macro(&ast)
}
// Main implementation macro, used to compute static values and define
// method for encoding the final eip712 payload;
fn impl_eip_712_macro(ast: &syn::DeriveInput) -> TokenStream {
// Primary type should match the type in the ethereum verifying contract;
let primary_type = &ast.ident;
// Instantiate domain from parsed attributes
let domain = match eip712::EIP712Domain::try_from(ast) {
Ok(attributes) => attributes,
Err(e) => return TokenStream::from(e),
};
let domain_separator = hex::encode(domain.separator());
//
let domain_str = match serde_json::to_string(&domain) {
Ok(s) => s,
Err(e) => {
return TokenStream::from(
syn::Error::new(ast.ident.span(), e.to_string()).to_compile_error(),
)
}
};
// Must parse the AST at compile time.
let parsed_fields = match eip712::parse_fields(ast) {
Ok(fields) => fields,
Err(e) => return TokenStream::from(e),
};
// Compute the type hash for the derived struct using the parsed fields from above.
let type_hash =
hex::encode(eip712::make_type_hash(primary_type.clone().to_string(), &parsed_fields));
// Use reference to ethers_core instead of directly using the crate itself.
let ethers_core = ethers_core_crate();
let implementation = quote! {
impl Eip712 for #primary_type {
type Error = #ethers_core::types::transaction::eip712::Eip712Error;
fn type_hash() -> Result<[u8; 32], Self::Error> {
use std::convert::TryFrom;
let decoded = #ethers_core::utils::hex::decode(#type_hash)?;
let byte_array: [u8; 32] = <[u8; 32]>::try_from(&decoded[..])?;
Ok(byte_array)
}
// Return the pre-computed domain separator from compile time;
fn domain_separator(&self) -> Result<[u8; 32], Self::Error> {
use std::convert::TryFrom;
let decoded = #ethers_core::utils::hex::decode(#domain_separator)?;
let byte_array: [u8; 32] = <[u8; 32]>::try_from(&decoded[..])?;
Ok(byte_array)
}
fn domain(&self) -> Result<#ethers_core::types::transaction::eip712::EIP712Domain, Self::Error> {
let domain: #ethers_core::types::transaction::eip712::EIP712Domain = # ethers_core::utils::__serde_json::from_str(#domain_str)?;
Ok(domain)
}
fn struct_hash(&self) -> Result<[u8; 32], Self::Error> {
use #ethers_core::abi::Tokenizable;
let mut items = vec![#ethers_core::abi::Token::Uint(
#ethers_core::types::U256::from(&Self::type_hash()?[..]),
)];
if let #ethers_core::abi::Token::Tuple(tokens) = self.clone().into_token() {
for token in tokens {
match &token {
#ethers_core::abi::Token::Tuple(t) => {
// TODO: check for nested Eip712 Type;
// Challenge is determining the type hash
return Err(Self::Error::NestedEip712StructNotImplemented);
},
_ => {
items.push(#ethers_core::types::transaction::eip712::encode_eip712_type(token));
}
}
}
}
let struct_hash = #ethers_core::utils::keccak256(#ethers_core::abi::encode(
&items,
));
Ok(struct_hash)
}
}
};
implementation.into()
}

View File

@ -220,7 +220,6 @@ pub enum EthersCrate {
EthersContractAbigen,
EthersContractDerive,
EthersCore,
EthersDeriveEip712,
EthersEtherscan,
EthersMiddleware,
EthersProviders,
@ -250,7 +249,6 @@ impl EthersCrate {
Self::EthersContractAbigen => "ethers-contract-abigen",
Self::EthersContractDerive => "ethers-contract-derive",
Self::EthersCore => "ethers-core",
Self::EthersDeriveEip712 => "ethers-derive-eip712",
Self::EthersEtherscan => "ethers-etherscan",
Self::EthersMiddleware => "ethers-middleware",
Self::EthersProviders => "ethers-providers",
@ -268,7 +266,6 @@ impl EthersCrate {
Self::EthersContractAbigen => "::ethers_contract_abigen",
Self::EthersContractDerive => "::ethers_contract_derive",
Self::EthersCore => "::ethers_core",
Self::EthersDeriveEip712 => "::ethers_derive_eip712",
Self::EthersEtherscan => "::ethers_etherscan",
Self::EthersMiddleware => "::ethers_middleware",
Self::EthersProviders => "::ethers_providers",
@ -284,7 +281,6 @@ impl EthersCrate {
// re-exported in ethers::contract
Self::EthersContractAbigen => "::ethers::contract", // partially
Self::EthersContractDerive => "::ethers::contract",
Self::EthersDeriveEip712 => "::ethers::contract",
Self::EthersAddressbook => "::ethers::addressbook",
Self::EthersContract => "::ethers::contract",
@ -303,7 +299,6 @@ impl EthersCrate {
match self {
Self::EthersContractAbigen => "ethers-contract/ethers-contract-abigen",
Self::EthersContractDerive => "ethers-contract/ethers-contract-derive",
Self::EthersDeriveEip712 => "ethers-core/ethers-derive-eip712",
_ => self.crate_name(),
}
}

View File

@ -4,43 +4,35 @@ use crate::{
types::{serde_helpers::StringifiedNumeric, Address, Bytes, U256},
utils::keccak256,
};
use convert_case::{Case, Casing};
use core::convert::TryFrom;
use ethabi::encode;
use proc_macro2::TokenStream;
use serde::{Deserialize, Deserializer, Serialize};
use std::{
collections::{BTreeMap, HashSet},
convert::TryInto,
iter::FromIterator,
};
use syn::{
parse::Error, spanned::Spanned as _, AttrStyle, Data, DeriveInput, Expr, Fields,
GenericArgument, Lit, NestedMeta, PathArguments, Type,
};
/// Custom types for `TypedData`
pub type Types = BTreeMap<String, Vec<Eip712DomainType>>;
/// Pre-computed value of the following statement:
/// Pre-computed value of the following expression:
///
/// `ethers_core::utils::keccak256("EIP712Domain(string name,string version,uint256 chainId,address
/// `keccak256("EIP712Domain(string name,string version,uint256 chainId,address
/// verifyingContract)")`
pub const EIP712_DOMAIN_TYPE_HASH: [u8; 32] = [
139, 115, 195, 198, 155, 184, 254, 61, 81, 46, 204, 76, 247, 89, 204, 121, 35, 159, 123, 23,
155, 15, 250, 202, 169, 167, 93, 82, 43, 57, 64, 15,
];
/// Pre-computed value of the following statement:
/// Pre-computed value of the following expression:
///
/// `ethers_core::utils::keccak256("EIP712Domain(string name,string version,uint256 chainId,address
/// `keccak256("EIP712Domain(string name,string version,uint256 chainId,address
/// verifyingContract,bytes32 salt)")`
pub const EIP712_DOMAIN_TYPE_HASH_WITH_SALT: [u8; 32] = [
216, 124, 214, 239, 121, 212, 226, 185, 94, 21, 206, 138, 191, 115, 45, 181, 30, 199, 113, 241,
202, 46, 220, 207, 34, 164, 108, 114, 154, 197, 100, 114,
];
/// Error typed used by Eip712 derive macro
/// An EIP-712 error.
#[derive(Debug, thiserror::Error)]
pub enum Eip712Error {
#[error("Failed to serialize serde JSON object")]
@ -57,8 +49,7 @@ pub enum Eip712Error {
Message(String),
}
/// The Eip712 trait provides helper methods for computing
/// the typed data hash used in `eth_signTypedData`.
/// Helper methods for computing the typed data hash used in `eth_signTypedData`.
///
/// The ethers-rs `derive_eip712` crate provides a derive macro to
/// implement the trait for a given struct. See documentation
@ -245,233 +236,40 @@ impl<T: Eip712 + Clone> Eip712 for EIP712WithDomain<T> {
}
}
// Parse the AST of the struct to determine the domain attributes
impl TryFrom<&syn::DeriveInput> for EIP712Domain {
type Error = TokenStream;
fn try_from(input: &syn::DeriveInput) -> Result<EIP712Domain, Self::Error> {
let mut domain = EIP712Domain::default();
let mut found_eip712_attribute = false;
'attribute_search: for attribute in input.attrs.iter() {
if let AttrStyle::Outer = attribute.style {
if let Ok(syn::Meta::List(meta)) = attribute.parse_meta() {
if meta.path.is_ident("eip712") {
found_eip712_attribute = true;
for n in meta.nested.iter() {
if let NestedMeta::Meta(meta) = n {
match meta {
syn::Meta::NameValue(meta) => {
let ident = meta.path.get_ident().ok_or_else(|| {
Error::new(
meta.path.span(),
"unrecognized eip712 parameter",
)
.to_compile_error()
})?;
match ident.to_string().as_ref() {
"name" => match meta.lit {
syn::Lit::Str(ref lit_str) => {
if domain.name.is_some() {
return Err(Error::new(
meta.path.span(),
"domain name already specified",
)
.to_compile_error())
}
domain.name = Some(lit_str.value());
}
_ => {
return Err(Error::new(
meta.path.span(),
"domain name must be a string",
)
.to_compile_error())
}
},
"version" => match meta.lit {
syn::Lit::Str(ref lit_str) => {
if domain.version.is_some() {
return Err(Error::new(
meta.path.span(),
"domain version already specified",
)
.to_compile_error())
}
domain.version = Some(lit_str.value());
}
_ => {
return Err(Error::new(
meta.path.span(),
"domain version must be a string",
)
.to_compile_error())
}
},
"chain_id" => match meta.lit {
syn::Lit::Int(ref lit_int) => {
if domain.chain_id.is_some() {
return Err(Error::new(
meta.path.span(),
"domain chain_id already specified",
)
.to_compile_error())
}
domain.chain_id = Some(U256::from(
lit_int.base10_parse::<u64>().map_err(
|_| {
Error::new(
meta.path.span(),
"failed to parse chain id",
)
.to_compile_error()
},
)?,
));
}
_ => {
return Err(Error::new(
meta.path.span(),
"domain chain_id must be a positive integer",
)
.to_compile_error());
}
},
"verifying_contract" => match meta.lit {
syn::Lit::Str(ref lit_str) => {
if domain.verifying_contract.is_some() {
return Err(Error::new(
meta.path.span(),
"domain verifying_contract already specified",
)
.to_compile_error());
}
domain.verifying_contract = Some(lit_str.value().parse().map_err(|_| {
Error::new(
meta.path.span(),
"failed to parse verifying contract into Address",
)
.to_compile_error()
})?);
}
_ => {
return Err(Error::new(
meta.path.span(),
"domain verifying_contract must be a string",
)
.to_compile_error());
}
},
"salt" => match meta.lit {
syn::Lit::Str(ref lit_str) => {
if domain.salt.is_some() {
return Err(Error::new(
meta.path.span(),
"domain salt already specified",
)
.to_compile_error())
}
// keccak256(<string>) to compute bytes32
// encoded domain salt
let salt = keccak256(lit_str.value());
domain.salt = Some(salt);
}
_ => {
return Err(Error::new(
meta.path.span(),
"domain salt must be a string",
)
.to_compile_error())
}
},
_ => {
return Err(Error::new(
meta.path.span(),
"unrecognized eip712 parameter; must be one of 'name', 'version', 'chain_id', or 'verifying_contract'",
)
.to_compile_error());
}
}
}
syn::Meta::Path(path) => {
return Err(Error::new(
path.span(),
"unrecognized eip712 parameter",
)
.to_compile_error())
}
syn::Meta::List(meta) => {
return Err(Error::new(
meta.path.span(),
"unrecognized eip712 parameter",
)
.to_compile_error())
}
}
}
}
}
break 'attribute_search
}
}
}
if !found_eip712_attribute {
return Err(Error::new_spanned(
input,
"missing required derive attribute: '#[eip712( ... )]'".to_string(),
)
.to_compile_error())
}
Ok(domain)
}
}
/// Represents the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) typed data object.
///
/// Typed data is a JSON object containing type information, domain separator parameters and the
/// message object which has the following schema
///
/// ```js
/// ```json
/// {
// type: 'object',
// properties: {
// types: {
// type: 'object',
// properties: {
// EIP712Domain: {type: 'array'},
// },
// additionalProperties: {
// type: 'array',
// items: {
// type: 'object',
// properties: {
// name: {type: 'string'},
// type: {type: 'string'}
// },
// required: ['name', 'type']
// }
// },
// required: ['EIP712Domain']
// },
// primaryType: {type: 'string'},
// domain: {type: 'object'},
// message: {type: 'object'}
// },
// required: ['types', 'primaryType', 'domain', 'message']
// }
/// "type": "object",
/// "properties": {
/// "types": {
/// "type": "object",
/// "properties": {
/// "EIP712Domain": { "type": "array" }
/// },
/// "additionalProperties": {
/// "type": "array",
/// "items": {
/// "type": "object",
/// "properties": {
/// "name": { "type": "string" },
/// "type": { "type": "string" }
/// },
/// "required": ["name", "type"]
/// }
/// },
/// "required": ["EIP712Domain"]
/// },
/// "primaryType": { "type": "string" },
/// "domain": { "type": "object" },
/// "message": { "type": "object" }
/// },
/// "required": ["types", "primaryType", "domain", "message"]
/// }
/// ```
///
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct TypedData {
@ -772,137 +570,6 @@ pub fn encode_field(
Ok(token)
}
/// Parse the eth abi parameter type based on the syntax type;
/// this method is copied from <https://github.com/gakonst/ethers-rs/blob/master/ethers-contract/ethers-contract-derive/src/lib.rs#L600>
/// with additional modifications for finding byte arrays
pub fn find_parameter_type(ty: &Type) -> Result<ParamType, TokenStream> {
match ty {
Type::Array(ty) => {
let param = find_parameter_type(ty.elem.as_ref())?;
if let Expr::Lit(ref expr) = ty.len {
if let Lit::Int(ref len) = expr.lit {
if let Ok(size) = len.base10_parse::<usize>() {
if let ParamType::Uint(_) = param {
return Ok(ParamType::FixedBytes(size))
}
return Ok(ParamType::FixedArray(Box::new(param), size))
}
}
}
Err(Error::new(ty.span(), "Failed to derive proper ABI from array field")
.to_compile_error())
}
Type::Path(ty) => {
if let Some(ident) = ty.path.get_ident() {
let ident = ident.to_string().to_lowercase();
return match ident.as_str() {
"address" => Ok(ParamType::Address),
"string" => Ok(ParamType::String),
"bool" => Ok(ParamType::Bool),
"int256" | "int" | "uint" | "uint256" => Ok(ParamType::Uint(256)),
"h160" => Ok(ParamType::FixedBytes(20)),
"h256" | "secret" | "hash" => Ok(ParamType::FixedBytes(32)),
"h512" | "public" => Ok(ParamType::FixedBytes(64)),
"bytes" => Ok(ParamType::Bytes),
s => parse_int_param_type(s).ok_or_else(|| {
Error::new(
ty.span(),
format!("Failed to derive proper ABI from field: {s})"),
)
.to_compile_error()
}),
}
}
// check for `Vec`
if ty.path.segments.len() == 1 && ty.path.segments[0].ident == "Vec" {
if let PathArguments::AngleBracketed(ref args) = ty.path.segments[0].arguments {
if args.args.len() == 1 {
if let GenericArgument::Type(ref ty) = args.args.iter().next().unwrap() {
let kind = find_parameter_type(ty)?;
// Check if byte array is found
if let ParamType::Uint(size) = kind {
if size == 8 {
return Ok(ParamType::Bytes)
}
}
return Ok(ParamType::Array(Box::new(kind)))
}
}
}
}
Err(Error::new(ty.span(), "Failed to derive proper ABI from fields").to_compile_error())
}
Type::Tuple(ty) => {
let params = ty.elems.iter().map(find_parameter_type).collect::<Result<Vec<_>, _>>()?;
Ok(ParamType::Tuple(params))
}
_ => {
Err(Error::new(ty.span(), "Failed to derive proper ABI from fields").to_compile_error())
}
}
}
fn parse_int_param_type(s: &str) -> Option<ParamType> {
let size = s.chars().skip(1).collect::<String>().parse::<usize>().ok()?;
if s.starts_with('u') {
Some(ParamType::Uint(size))
} else if s.starts_with('i') {
Some(ParamType::Int(size))
} else {
None
}
}
/// Return HashMap of the field name and the field type;
pub fn parse_fields(ast: &DeriveInput) -> Result<Vec<(String, ParamType)>, TokenStream> {
let mut fields = Vec::new();
let data = match &ast.data {
Data::Struct(s) => s,
_ => {
return Err(Error::new(
ast.span(),
"invalid data type. can only derive Eip712 for a struct",
)
.to_compile_error())
}
};
let named_fields = match &data.fields {
Fields::Named(name) => name,
_ => {
return Err(Error::new(ast.span(), "unnamed fields are not supported").to_compile_error())
}
};
for f in named_fields.named.iter() {
let field_name =
f.ident.clone().map(|i| i.to_string().to_case(Case::Camel)).ok_or_else(|| {
Error::new(named_fields.span(), "fields must be named").to_compile_error()
})?;
let field_type =
match f.attrs.iter().find(|a| a.path.segments.iter().any(|s| s.ident == "eip712")) {
// Found nested Eip712 Struct
// TODO: Implement custom
Some(a) => {
return Err(Error::new(a.span(), "nested Eip712 struct are not yet supported")
.to_compile_error())
}
// Not a nested eip712 struct, return the field param type;
None => find_parameter_type(&f.ty)?,
};
fields.push((field_name, field_type));
}
Ok(fields)
}
/// Convert hash map of field names and types into a type hash corresponding to enc types;
pub fn make_type_hash(primary_type: String, fields: &[(String, ParamType)]) -> [u8; 32] {
let parameters =

View File

@ -5,7 +5,6 @@ pub mod eip1559;
pub mod eip2718;
pub mod eip2930;
#[cfg(feature = "eip712")]
pub mod eip712;
pub(crate) const BASE_NUM_TX_FIELDS: usize = 9;

View File

@ -52,11 +52,9 @@ const OVERFLOW_I256_UNITS: usize = 77;
/// U256 overflows for numbers wider than 78 units.
const OVERFLOW_U256_UNITS: usize = 78;
/// Re-export of serde-json
// Re-export serde-json for macro usage
#[doc(hidden)]
pub mod __serde_json {
pub use serde_json::*;
}
pub use serde_json as __serde_json;
#[derive(Error, Debug)]
pub enum ConversionError {

View File

@ -24,7 +24,7 @@ rustdoc-args = ["--cfg", "docsrs"]
all-features = true
[dependencies]
ethers-core = { workspace = true, features = ["eip712"] }
ethers-core.workspace = true
# crypto
coins-bip32 = "0.8.3"
@ -66,7 +66,6 @@ yubihsm = { version = "0.42.0-pre.0", features = ["secp256k1", "http", "usb"], o
[dev-dependencies]
ethers-contract-derive.workspace = true
ethers-derive-eip712.workspace = true
serde_json.workspace = true
tempfile.workspace = true

View File

@ -293,11 +293,10 @@ impl LedgerEthereum {
mod tests {
use super::*;
use crate::Signer;
use ethers_contract_derive::EthAbiType;
use ethers_contract_derive::{Eip712, EthAbiType};
use ethers_core::types::{
transaction::eip712::Eip712, Address, TransactionRequest, I256, U256,
};
use ethers_derive_eip712::*;
use std::str::FromStr;
#[derive(Debug, Clone, Eip712, EthAbiType)]

View File

@ -233,7 +233,7 @@ impl TrezorEthereum {
mod tests {
use super::*;
use crate::Signer;
use ethers_contract_derive::EthAbiType;
use ethers_contract_derive::{Eip712, EthAbiType};
use ethers_core::types::{
transaction::{
eip2930::{AccessList, AccessListItem},
@ -241,7 +241,6 @@ mod tests {
},
Address, Eip1559TransactionRequest, TransactionRequest, I256, U256,
};
use ethers_derive_eip712::*;
use std::str::FromStr;
#[derive(Debug, Clone, Eip712, EthAbiType)]

View File

@ -26,18 +26,35 @@ all-features = true
[features]
default = ["abigen", "rustls"]
celo = ["ethers-core/celo", "ethers-providers/celo", "ethers-signers/celo", "ethers-contract/celo", "ethers-middleware/celo", "legacy"]
celo = [
"ethers-core/celo",
"ethers-providers/celo",
"ethers-signers/celo",
"ethers-contract/celo",
"ethers-middleware/celo",
"legacy",
]
legacy = ["ethers-core/legacy", "ethers-contract/legacy"]
# individual features per sub-crate
## core
eip712 = ["ethers-contract/eip712", "ethers-core/eip712"]
## providers
ws = ["ethers-providers/ws"]
ipc = ["ethers-providers/ipc"]
rustls = ["ethers-middleware/rustls", "ethers-providers/rustls", "ethers-etherscan/rustls", "ethers-contract/rustls", "ethers-solc/rustls"]
openssl = ["ethers-middleware/openssl", "ethers-providers/openssl", "ethers-etherscan/openssl", "ethers-contract/openssl", "ethers-solc/openssl"]
rustls = [
"ethers-middleware/rustls",
"ethers-providers/rustls",
"ethers-etherscan/rustls",
"ethers-contract/rustls",
"ethers-solc/rustls",
]
openssl = [
"ethers-middleware/openssl",
"ethers-providers/openssl",
"ethers-etherscan/openssl",
"ethers-contract/openssl",
"ethers-solc/openssl",
]
dev-rpc = ["ethers-providers/dev-rpc"]
## signers
ledger = ["ethers-signers/ledger"]
@ -67,6 +84,6 @@ ethers-solc.workspace = true
[dev-dependencies]
serde.workspace = true
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
ethers-contract = { workspace = true, features = ["eip712"] }
ethers-contract.workspace = true
ethers-providers = { workspace = true, features = ["rustls"] } # allow https connections
ethers-solc = { workspace = true, features = ["svm-solc"] }

View File

@ -1,158 +0,0 @@
use ethers::{
contract::{abigen, ContractFactory, Eip712, EthAbiType},
core::{
types::{transaction::eip712::Eip712, Address, Bytes, I256, U256},
utils::{keccak256, Anvil},
},
providers::Provider,
signers::LocalWallet,
solc::Solc,
};
use std::{path::PathBuf, sync::Arc};
#[tokio::test(flavor = "multi_thread")]
async fn test_derive_eip712() {
// Generate Contract ABI Bindings
abigen!(
DeriveEip712Test,
"./ethers-contract/tests/solidity-contracts/derive_eip712_abi.json",
event_derives(serde::Deserialize, serde::Serialize)
);
// Create derived structs
#[derive(Debug, Clone, Eip712, EthAbiType)]
#[eip712(
name = "Eip712Test",
version = "1",
chain_id = 1,
verifying_contract = "0x0000000000000000000000000000000000000001",
salt = "eip712-test-75F0CCte"
)]
struct FooBar {
foo: I256,
bar: U256,
fizz: Bytes,
buzz: [u8; 32],
far: String,
out: Address,
}
// get ABI and bytecode for the DeriveEip712Test contract
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests");
Solc::find_or_install_svm_version("0.6.0").unwrap(); // install solc
let result = Solc::default().compile_source(path).unwrap();
let (abi, bytecode, _) = result
.find("DeriveEip712Test")
.expect("failed to get DeriveEip712Test contract")
.into_parts_or_default();
// launch the network & connect to it
let anvil = Anvil::new().spawn();
let from = anvil.addresses()[0];
let provider = Provider::try_from(anvil.endpoint())
.unwrap()
.with_sender(from)
.interval(std::time::Duration::from_millis(10));
let client = Arc::new(provider);
let factory = ContractFactory::new(abi.clone(), bytecode.clone(), client.clone());
let contract = factory
.deploy(())
.expect("failed to deploy DeriveEip712Test contract")
.legacy()
.send()
.await
.expect("failed to instantiate factory for DeriveEip712 contract");
let addr = contract.address();
let contract = DeriveEip712Test::new(addr, client.clone());
let foo_bar = FooBar {
foo: I256::from(10u64),
bar: U256::from(20u64),
fizz: b"fizz".into(),
buzz: keccak256("buzz"),
far: String::from("space"),
out: Address::from([0; 20]),
};
let derived_foo_bar = derive_eip_712_test::FooBar {
foo: foo_bar.foo,
bar: foo_bar.bar,
fizz: foo_bar.fizz.clone(),
buzz: foo_bar.buzz,
far: foo_bar.far.clone(),
out: foo_bar.out,
};
use ethers::signers::Signer;
let wallet: LocalWallet = anvil.keys()[0].clone().into();
let sig = wallet.sign_typed_data(&foo_bar).await.expect("failed to sign typed data");
let r = <[u8; 32]>::try_from(sig.r)
.expect("failed to parse 'r' value from signature into [u8; 32]");
let s = <[u8; 32]>::try_from(sig.s)
.expect("failed to parse 's' value from signature into [u8; 32]");
let v = u8::try_from(sig.v).expect("failed to parse 'v' value from signature into u8");
let domain_separator = contract
.domain_separator()
.call()
.await
.expect("failed to retrieve domain_separator from contract");
let type_hash =
contract.type_hash().call().await.expect("failed to retrieve type_hash from contract");
let struct_hash = contract
.struct_hash(derived_foo_bar.clone())
.call()
.await
.expect("failed to retrieve struct_hash from contract");
let encoded = contract
.encode_eip_712(derived_foo_bar.clone())
.call()
.await
.expect("failed to retrieve eip712 encoded hash from contract");
let verify = contract
.verify_foo_bar(wallet.address(), derived_foo_bar, r, s, v)
.call()
.await
.expect("failed to verify signed typed data eip712 payload");
assert_eq!(
domain_separator,
foo_bar
.domain()
.expect("failed to return domain_separator from Eip712 implemented struct")
.separator(),
"domain separator does not match contract domain separator!"
);
assert_eq!(
type_hash,
FooBar::type_hash().expect("failed to return type_hash from Eip712 implemented struct"),
"type hash does not match contract struct type hash!"
);
assert_eq!(
struct_hash,
foo_bar
.clone()
.struct_hash()
.expect("failed to return struct_hash from Eip712 implemented struct"),
"struct hash does not match contract struct hash!"
);
assert_eq!(
encoded,
foo_bar
.encode_eip712()
.expect("failed to return domain_separator from Eip712 implemented struct"),
"Encoded value does not match!"
);
assert!(verify, "typed data signature failed!");
}

View File

@ -1,4 +0,0 @@
//! Ethers integration tests.
#![cfg(not(target_arch = "wasm32"))]
mod eip712;

View File

@ -15,7 +15,7 @@ trezor = ["ethers/trezor"]
yubi = ["ethers/yubi"]
[dev-dependencies]
ethers = { workspace = true, features = ["abigen", "eip712", "ws", "rustls"] }
ethers = { workspace = true, features = ["abigen", "ws", "rustls"] }
tokio = { workspace = true, features = ["macros"] }