progress
This commit is contained in:
parent
25f2c5e45d
commit
099fa5d7ef
|
@ -1,5 +1,22 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "Inflector"
|
||||||
|
version = "0.11.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.31"
|
version = "1.0.31"
|
||||||
|
@ -114,6 +131,34 @@ dependencies = [
|
||||||
"sct 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"sct 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "curl"
|
||||||
|
version = "0.4.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"curl-sys 0.4.31+curl-7.70.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"openssl-sys 0.9.57 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "curl-sys"
|
||||||
|
version = "0.4.31+curl-7.70.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"openssl-sys 0.9.57 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dtoa"
|
name = "dtoa"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
@ -204,11 +249,44 @@ name = "ethers-contract"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ethers-abi 0.1.0",
|
"ethers-abi 0.1.0",
|
||||||
|
"ethers-contract-abigen 0.1.0",
|
||||||
|
"ethers-contract-derive 0.1.0",
|
||||||
"ethers-providers 0.1.0",
|
"ethers-providers 0.1.0",
|
||||||
"ethers-signers 0.1.0",
|
"ethers-signers 0.1.0",
|
||||||
"ethers-types 0.1.0",
|
"ethers-types 0.1.0",
|
||||||
|
"once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rustc-hex 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-hex 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ethers-contract-abigen"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"Inflector 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"curl 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"ethers-abi 0.1.0",
|
||||||
|
"ethers-types 0.1.0",
|
||||||
|
"once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"syn 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ethers-contract-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ethers-abi 0.1.0",
|
||||||
|
"ethers-contract-abigen 0.1.0",
|
||||||
|
"proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"syn 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -216,6 +294,7 @@ name = "ethers-providers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
"async-trait 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"ethers-abi 0.1.0",
|
||||||
"ethers-types 0.1.0",
|
"ethers-types 0.1.0",
|
||||||
"ethers-utils 0.1.0",
|
"ethers-utils 0.1.0",
|
||||||
"reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -521,6 +600,17 @@ name = "libc"
|
||||||
version = "0.2.70"
|
version = "0.2.70"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libz-sys"
|
||||||
|
version = "1.0.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.8"
|
version = "0.4.8"
|
||||||
|
@ -602,6 +692,18 @@ name = "openssl-probe"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.57"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parity-scale-codec"
|
name = "parity-scale-codec"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
@ -646,6 +748,11 @@ name = "pin-utils"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
|
@ -825,6 +932,27 @@ dependencies = [
|
||||||
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.1.56"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
|
@ -1021,6 +1149,17 @@ name = "smallvec"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.3.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
@ -1059,6 +1198,14 @@ dependencies = [
|
||||||
"syn 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
"syn 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.43"
|
version = "0.1.43"
|
||||||
|
@ -1200,6 +1347,11 @@ dependencies = [
|
||||||
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
|
@ -1359,6 +1511,8 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
|
"checksum Inflector 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||||
|
"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
|
||||||
"checksum anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
|
"checksum anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
|
||||||
"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
|
"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
|
||||||
"checksum async-trait 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b"
|
"checksum async-trait 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b"
|
||||||
|
@ -1378,6 +1532,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
"checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
|
"checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
|
||||||
"checksum crunchy 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
"checksum crunchy 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||||
"checksum ct-logs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4d3686f5fa27dbc1d76c751300376e167c5a43387f44bb451fd1c24776e49113"
|
"checksum ct-logs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4d3686f5fa27dbc1d76c751300376e167c5a43387f44bb451fd1c24776e49113"
|
||||||
|
"checksum curl 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)" = "762e34611d2d5233a506a79072be944fddd057db2f18e04c0d6fa79e3fd466fd"
|
||||||
|
"checksum curl-sys 0.4.31+curl-7.70.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dcd62757cc4f5ab9404bc6ca9f0ae447e729a1403948ce5106bd588ceac6a3b0"
|
||||||
"checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
|
"checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
|
||||||
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
|
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
|
||||||
"checksum encoding_rs 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171"
|
"checksum encoding_rs 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171"
|
||||||
|
@ -1413,6 +1569,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
"checksum libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)" = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f"
|
"checksum libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)" = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f"
|
||||||
|
"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe"
|
||||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||||
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||||
"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||||
|
@ -1423,12 +1580,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
"checksum net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7"
|
"checksum net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7"
|
||||||
"checksum once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d"
|
"checksum once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d"
|
||||||
"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
|
"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
|
||||||
|
"checksum openssl-sys 0.9.57 (registry+https://github.com/rust-lang/crates.io-index)" = "7410fef80af8ac071d4f63755c0ab89ac3df0fd1ea91f1d1f37cf5cec4395990"
|
||||||
"checksum parity-scale-codec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "329c8f7f4244ddb5c37c103641027a76c530e65e8e4b8240b29f81ea40508b17"
|
"checksum parity-scale-codec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "329c8f7f4244ddb5c37c103641027a76c530e65e8e4b8240b29f81ea40508b17"
|
||||||
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
"checksum pin-project 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791"
|
"checksum pin-project 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791"
|
||||||
"checksum pin-project-internal 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40"
|
"checksum pin-project-internal 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40"
|
||||||
"checksum pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f"
|
"checksum pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f"
|
||||||
"checksum pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
"checksum pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
|
||||||
"checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
|
"checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
|
||||||
"checksum primitive-types 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c55c21c64d0eaa4d7ed885d959ef2d62d9e488c27c0e02d9aa5ce6c877b7d5f8"
|
"checksum primitive-types 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c55c21c64d0eaa4d7ed885d959ef2d62d9e488c27c0e02d9aa5ce6c877b7d5f8"
|
||||||
"checksum proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)" = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
|
"checksum proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)" = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
|
||||||
|
@ -1450,6 +1609,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
|
"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
|
||||||
"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
|
"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
|
||||||
"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
|
"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
|
||||||
|
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||||
|
"checksum regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
|
||||||
|
"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
|
||||||
"checksum reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2"
|
"checksum reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2"
|
||||||
"checksum ring 0.16.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1ba5a8ec64ee89a76c98c549af81ff14813df09c3e6dc4766c3856da48597a0c"
|
"checksum ring 0.16.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1ba5a8ec64ee89a76c98c549af81ff14813df09c3e6dc4766c3856da48597a0c"
|
||||||
"checksum rlp 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4a7d3f9bed94764eac15b8f14af59fac420c236adaff743b7bcc88e265cb4345"
|
"checksum rlp 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4a7d3f9bed94764eac15b8f14af59fac420c236adaff743b7bcc88e265cb4345"
|
||||||
|
@ -1469,11 +1631,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
"checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
|
"checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
|
||||||
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||||
"checksum smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
|
"checksum smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
|
||||||
|
"checksum socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918"
|
||||||
"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
"checksum static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
"checksum static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
"checksum syn 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f14a640819f79b72a710c0be059dce779f9339ae046c8bef12c361d56702146f"
|
"checksum syn 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f14a640819f79b72a710c0be059dce779f9339ae046c8bef12c361d56702146f"
|
||||||
"checksum thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344"
|
"checksum thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344"
|
||||||
"checksum thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479"
|
"checksum thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479"
|
||||||
|
"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||||
"checksum time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
|
"checksum time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
|
||||||
"checksum tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d8a021c69bb74a44ccedb824a046447e2c84a01df9e5c20779750acb38e11b2"
|
"checksum tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d8a021c69bb74a44ccedb824a046447e2c84a01df9e5c20779750acb38e11b2"
|
||||||
"checksum tiny-keccak 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
"checksum tiny-keccak 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||||
|
@ -1490,6 +1654,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||||
"checksum untrusted 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
"checksum untrusted 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||||
"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
|
"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
|
||||||
|
"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
|
||||||
"checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
"checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||||
"checksum want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
|
"checksum want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
|
||||||
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||||
|
|
|
@ -4,7 +4,6 @@ members = [
|
||||||
"./crates/ethers",
|
"./crates/ethers",
|
||||||
"./crates/ethers-abi",
|
"./crates/ethers-abi",
|
||||||
"./crates/ethers-contract",
|
"./crates/ethers-contract",
|
||||||
# "./crates/ethers-derive",
|
|
||||||
"./crates/ethers-providers",
|
"./crates/ethers-providers",
|
||||||
"./crates/ethers-signers",
|
"./crates/ethers-signers",
|
||||||
"./crates/ethers-types",
|
"./crates/ethers-types",
|
||||||
|
|
|
@ -11,6 +11,7 @@ Complete Ethereum wallet implementation and utilities in Rust (with WASM and FFI
|
||||||
- [ ] Deploy and interact with smart contracts
|
- [ ] Deploy and interact with smart contracts
|
||||||
- [ ] Type safe smart contract bindings
|
- [ ] Type safe smart contract bindings
|
||||||
- [ ] Hardware wallet support
|
- [ ] Hardware wallet support
|
||||||
|
- [ ] CLI for creating transactions, interacting with contracts, generating bindings from ABIs (abigen equivalent), ...
|
||||||
- [ ] ...
|
- [ ] ...
|
||||||
|
|
||||||
## Directory Structure
|
## Directory Structure
|
||||||
|
|
|
@ -9,7 +9,7 @@ use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Error)]
|
#[derive(Clone, Debug, Error)]
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
pub struct InvalidOutputType(String);
|
pub struct InvalidOutputType(pub String);
|
||||||
|
|
||||||
/// Output type possible to deserialize from Contract ABI
|
/// Output type possible to deserialize from Contract ABI
|
||||||
pub trait Detokenize {
|
pub trait Detokenize {
|
||||||
|
|
|
@ -6,6 +6,7 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ethers-contract-abigen = { path = "ethers-contract-abigen", optional = true }
|
ethers-contract-abigen = { path = "ethers-contract-abigen", optional = true }
|
||||||
|
ethers-contract-derive = { path = "ethers-contract-derive", optional = true }
|
||||||
|
|
||||||
ethers-abi = { path = "../ethers-abi" }
|
ethers-abi = { path = "../ethers-abi" }
|
||||||
ethers-providers = { path = "../ethers-providers" }
|
ethers-providers = { path = "../ethers-providers" }
|
||||||
|
@ -15,7 +16,8 @@ ethers-types = { path = "../ethers-types" }
|
||||||
serde = { version = "1.0.110", default-features = false }
|
serde = { version = "1.0.110", default-features = false }
|
||||||
rustc-hex = { version = "2.1.0", default-features = false }
|
rustc-hex = { version = "2.1.0", default-features = false }
|
||||||
thiserror = { version = "1.0.19", default-features = false }
|
thiserror = { version = "1.0.19", default-features = false }
|
||||||
|
once_cell = "1.4.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["abigen"]
|
||||||
abigen = ["ethers-contract-abigen"]
|
abigen = ["ethers-contract-abigen", "ethers-contract-derive"]
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ethers-derive"
|
name = "ethers-contract-abigen"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Nicholas Rodrigues Lordello <nlordell@gmail.com>", "Georgios Konstantopoulos <me@gakonst.com>"]
|
authors = ["Nicholas Rodrigues Lordello <nlordell@gmail.com>", "Georgios Konstantopoulos <me@gakonst.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/gnosis/ethcontract-rs"
|
|
||||||
homepage = "https://github.com/gnosis/ethcontract-rs"
|
|
||||||
documentation = "https://docs.rs/ethcontract"
|
|
||||||
description = "Code generation for type-safe bindings to Ethereum smart contracts"
|
description = "Code generation for type-safe bindings to Ethereum smart contracts"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ethers-abi = { path = "../ethers-abi" }
|
ethers-abi = { path = "../../ethers-abi" }
|
||||||
|
ethers-types = { path = "../../ethers-types" }
|
||||||
|
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
curl = "0.4"
|
curl = "0.4"
|
||||||
|
@ -19,3 +17,5 @@ proc-macro2 = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = "1.0.12"
|
syn = "1.0.12"
|
||||||
url = "2.1"
|
url = "2.1"
|
||||||
|
serde_json = "1.0.53"
|
||||||
|
once_cell = "1.4.0"
|
|
@ -0,0 +1,12 @@
|
||||||
|
use ethers_contract_abigen::Builder;
|
||||||
|
|
||||||
|
const ABI: &'static str = r#"[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"name","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"symbol","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"decimals","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"totalSupply","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]"#;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let builder = Builder::from_str("ERC20Token", ABI);
|
||||||
|
builder
|
||||||
|
.generate()
|
||||||
|
.unwrap()
|
||||||
|
.write_to_file("token.rs")
|
||||||
|
.unwrap();
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
//! Crate for generating type-safe bindings to Ethereum smart contracts. This
|
||||||
|
//! crate is intended to be used either indirectly with the `ethcontract`
|
||||||
|
//! crate's `contract` procedural macro or directly from a build script.
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
mod events;
|
||||||
|
mod methods;
|
||||||
|
mod types;
|
||||||
|
|
||||||
|
use super::util;
|
||||||
|
use super::Args;
|
||||||
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
|
use ethers_abi::Abi;
|
||||||
|
use ethers_types::Address;
|
||||||
|
use inflector::Inflector;
|
||||||
|
use proc_macro2::{Ident, Literal, TokenStream};
|
||||||
|
use quote::quote;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use syn::{Path, Visibility};
|
||||||
|
|
||||||
|
/// Internal shared context for generating smart contract bindings.
|
||||||
|
pub(crate) struct Context {
|
||||||
|
/// The contract name
|
||||||
|
name: String,
|
||||||
|
|
||||||
|
/// The ABI string pre-parsing.
|
||||||
|
abi_str: Literal,
|
||||||
|
|
||||||
|
/// The parsed ABI.
|
||||||
|
abi: Abi,
|
||||||
|
|
||||||
|
/// The identifier for the runtime crate. Usually this is `ethcontract` but
|
||||||
|
/// it can be different if the crate was renamed in the Cargo manifest for
|
||||||
|
/// example.
|
||||||
|
runtime_crate: Ident,
|
||||||
|
|
||||||
|
/// The visibility for the generated module and re-exported contract type.
|
||||||
|
visibility: Visibility,
|
||||||
|
|
||||||
|
/// The contract name as an identifier.
|
||||||
|
contract_name: Ident,
|
||||||
|
|
||||||
|
/// Manually specified method aliases.
|
||||||
|
method_aliases: HashMap<String, Ident>,
|
||||||
|
|
||||||
|
/// Derives added to event structs and enums.
|
||||||
|
event_derives: Vec<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
pub fn expand(args: Args) -> Result<TokenStream> {
|
||||||
|
let cx = Self::from_args(args)?;
|
||||||
|
let name = &cx.contract_name;
|
||||||
|
let name_mod = util::ident(&format!(
|
||||||
|
"{}_mod",
|
||||||
|
cx.contract_name.to_string().to_lowercase()
|
||||||
|
));
|
||||||
|
|
||||||
|
// 0. Imports
|
||||||
|
let imports = common::imports();
|
||||||
|
|
||||||
|
// 1. Declare Contract struct
|
||||||
|
let struct_decl = common::struct_declaration(&cx);
|
||||||
|
|
||||||
|
// 2. Declare events structs & impl FromTokens for each event
|
||||||
|
let events_decl = cx.events_declaration()?;
|
||||||
|
|
||||||
|
// 3. impl block for the event functions
|
||||||
|
let contract_events = cx.events()?;
|
||||||
|
|
||||||
|
// 4. impl block for the contract methods
|
||||||
|
let contract_methods = cx.methods()?;
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
// export all the created data types
|
||||||
|
pub use #name_mod::*;
|
||||||
|
|
||||||
|
mod #name_mod {
|
||||||
|
#imports
|
||||||
|
|
||||||
|
#struct_decl
|
||||||
|
|
||||||
|
impl<'a, S: Signer, P: JsonRpcClient> #name<'a, S, P> {
|
||||||
|
/// Creates a new contract instance with the specified `ethers`
|
||||||
|
/// client at the given `Address`. The contract derefs to a `ethers::Contract`
|
||||||
|
/// object
|
||||||
|
pub fn new<T: Into<Address>>(address: T, client: &'a Client<'a, S, P>) -> Self {
|
||||||
|
let contract = Contract::new(client, &ABI, address.into());
|
||||||
|
Self(contract)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement deployment.
|
||||||
|
|
||||||
|
#contract_methods
|
||||||
|
|
||||||
|
#contract_events
|
||||||
|
}
|
||||||
|
|
||||||
|
#events_decl
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a context from the code generation arguments.
|
||||||
|
fn from_args(args: Args) -> Result<Self> {
|
||||||
|
// get the actual ABI string
|
||||||
|
let abi_str = args.abi_source.get().context("failed to get ABI JSON")?;
|
||||||
|
|
||||||
|
// parse it
|
||||||
|
let abi: Abi = serde_json::from_str(&abi_str)
|
||||||
|
.with_context(|| format!("invalid artifact JSON '{}'", abi_str))
|
||||||
|
.with_context(|| {
|
||||||
|
format!("failed to parse artifact from source {:?}", args.abi_source,)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let raw_contract_name = args.contract_name;
|
||||||
|
|
||||||
|
let runtime_crate = util::ident(&args.runtime_crate_name);
|
||||||
|
|
||||||
|
let visibility = match args.visibility_modifier.as_ref() {
|
||||||
|
Some(vis) => syn::parse_str(vis)?,
|
||||||
|
None => Visibility::Inherited,
|
||||||
|
};
|
||||||
|
|
||||||
|
let contract_name = util::ident(&raw_contract_name);
|
||||||
|
|
||||||
|
// NOTE: We only check for duplicate signatures here, since if there are
|
||||||
|
// duplicate aliases, the compiler will produce a warning because a
|
||||||
|
// method will be re-defined.
|
||||||
|
let mut method_aliases = HashMap::new();
|
||||||
|
for (signature, alias) in args.method_aliases.into_iter() {
|
||||||
|
let alias = syn::parse_str(&alias)?;
|
||||||
|
if method_aliases.insert(signature.clone(), alias).is_some() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"duplicate method signature '{}' in method aliases",
|
||||||
|
signature,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let event_derives = args
|
||||||
|
.event_derives
|
||||||
|
.iter()
|
||||||
|
.map(|derive| syn::parse_str::<Path>(derive))
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.context("failed to parse event derives")?;
|
||||||
|
|
||||||
|
Ok(Context {
|
||||||
|
name: raw_contract_name,
|
||||||
|
abi,
|
||||||
|
abi_str: Literal::string(&abi_str),
|
||||||
|
runtime_crate,
|
||||||
|
visibility,
|
||||||
|
contract_name,
|
||||||
|
method_aliases,
|
||||||
|
event_derives,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
use super::Context;
|
||||||
|
|
||||||
|
use ethers_types::Address;
|
||||||
|
use proc_macro2::{Literal, TokenStream};
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
pub(crate) fn imports() -> TokenStream {
|
||||||
|
quote! {
|
||||||
|
use ethers_contract::{
|
||||||
|
Sender, Event,
|
||||||
|
abi::{Abi, Token, Detokenize, InvalidOutputType, Tokenizable},
|
||||||
|
Contract, Lazy,
|
||||||
|
types::*, // import all the types so that we can codegen for everything
|
||||||
|
signers::{Signer, Client},
|
||||||
|
providers::JsonRpcClient,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn struct_declaration(cx: &Context) -> TokenStream {
|
||||||
|
let name = &cx.contract_name;
|
||||||
|
let abi = &cx.abi_str;
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
// Inline ABI declaration
|
||||||
|
static ABI: Lazy<Abi> = Lazy::new(|| serde_json::from_str(#abi)
|
||||||
|
.expect("invalid abi"));
|
||||||
|
|
||||||
|
// Struct declaration
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct #name<'a, S, P>(Contract<'a, S, P>);
|
||||||
|
|
||||||
|
|
||||||
|
// Deref to the inner contract in order to access more specific functions functions
|
||||||
|
impl<'a, S, P> std::ops::Deref for #name<'a, S, P> {
|
||||||
|
type Target = Contract<'a, S, P>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S: Signer, P: JsonRpcClient> std::fmt::Debug for #name<'a, S, P> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
f.debug_tuple(stringify!(#name))
|
||||||
|
.field(&self.address())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,566 @@
|
||||||
|
use super::{types, util, Context};
|
||||||
|
use ethers_abi::{Event, EventExt, EventParam, Hash, ParamType};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use inflector::Inflector;
|
||||||
|
use proc_macro2::{Literal, TokenStream};
|
||||||
|
use quote::quote;
|
||||||
|
use syn::Path;
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
/// Expands each event to a struct + its impl Detokenize block
|
||||||
|
pub fn events_declaration(&self) -> Result<TokenStream> {
|
||||||
|
let data_types = self
|
||||||
|
.abi
|
||||||
|
.events()
|
||||||
|
.map(|event| expand_event(event, &self.event_derives))
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
if data_types.is_empty() {
|
||||||
|
return Ok(quote! {});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
#( #data_types )*
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn events(&self) -> Result<TokenStream> {
|
||||||
|
let data_types = self
|
||||||
|
.abi
|
||||||
|
.events()
|
||||||
|
.map(|event| expand_filter(event))
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
if data_types.is_empty() {
|
||||||
|
return Ok(quote! {});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
#( #data_types )*
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands into a single method for contracting an event stream.
|
||||||
|
fn expand_filter(event: &Event) -> Result<TokenStream> {
|
||||||
|
let name = util::safe_ident(&event.name.to_snake_case());
|
||||||
|
let ev_name = Literal::string(&event.name);
|
||||||
|
let result = util::ident(&event.name.to_pascal_case());
|
||||||
|
|
||||||
|
let doc = util::expand_doc(&format!("Gets the {} event", event.name));
|
||||||
|
Ok(quote! {
|
||||||
|
|
||||||
|
#doc
|
||||||
|
pub fn #name<'b>(&'a self) -> Event<'a, 'b, P, #result> where 'a: 'b {
|
||||||
|
self.0.event(#ev_name).expect("event not found (this should never happen)")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands an ABI event into a single event data type. This can expand either
|
||||||
|
/// into a structure or a tuple in the case where all event parameters (topics
|
||||||
|
/// and data) are anonymous.
|
||||||
|
fn expand_event(event: &Event, event_derives: &[Path]) -> Result<TokenStream> {
|
||||||
|
let event_name = expand_struct_name(event);
|
||||||
|
|
||||||
|
let signature = expand_hash(event.signature());
|
||||||
|
|
||||||
|
let abi_signature = event.abi_signature();
|
||||||
|
let abi_signature_lit = Literal::string(&abi_signature);
|
||||||
|
let abi_signature_doc = util::expand_doc(&format!("`{}`", abi_signature));
|
||||||
|
|
||||||
|
let params = expand_params(event)?;
|
||||||
|
|
||||||
|
// expand as a tuple if all fields are anonymous
|
||||||
|
let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
|
||||||
|
let (data_type_definition, data_type_construction) = if all_anonymous_fields {
|
||||||
|
expand_data_tuple(&event_name, ¶ms)
|
||||||
|
} else {
|
||||||
|
expand_data_struct(&event_name, ¶ms)
|
||||||
|
};
|
||||||
|
|
||||||
|
// read each token parameter as the required data type
|
||||||
|
let params_len = Literal::usize_unsuffixed(params.len());
|
||||||
|
let read_param_token = params
|
||||||
|
.iter()
|
||||||
|
.map(|(name, ty)| {
|
||||||
|
quote! {
|
||||||
|
let #name = #ty::from_token(tokens.next().expect("this should never happen"))?;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let derives = expand_derives(event_derives);
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq, #derives)]
|
||||||
|
pub #data_type_definition
|
||||||
|
|
||||||
|
impl #event_name {
|
||||||
|
/// Retrieves the signature for the event this data corresponds to.
|
||||||
|
/// This signature is the Keccak-256 hash of the ABI signature of
|
||||||
|
/// this event.
|
||||||
|
pub const fn signature() -> H256 {
|
||||||
|
#signature
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the ABI signature for the event this data corresponds
|
||||||
|
/// to. For this event the value should always be:
|
||||||
|
///
|
||||||
|
#abi_signature_doc
|
||||||
|
pub const fn abi_signature() -> &'static str {
|
||||||
|
#abi_signature_lit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Detokenize for #event_name {
|
||||||
|
fn from_tokens(
|
||||||
|
tokens: Vec<Token>,
|
||||||
|
) -> Result<Self, InvalidOutputType> {
|
||||||
|
if tokens.len() != #params_len {
|
||||||
|
return Err(InvalidOutputType(format!(
|
||||||
|
"Expected {} tokens, got {}: {:?}",
|
||||||
|
#params_len,
|
||||||
|
tokens.len(),
|
||||||
|
tokens
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut tokens = tokens.into_iter();
|
||||||
|
#( #read_param_token )*
|
||||||
|
|
||||||
|
Ok(#data_type_construction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands an ABI event into an identifier for its event data type.
|
||||||
|
fn expand_struct_name(event: &Event) -> TokenStream {
|
||||||
|
let event_name = util::ident(&event.name.to_pascal_case());
|
||||||
|
quote! { #event_name }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands an ABI event into name-type pairs for each of its parameters.
|
||||||
|
fn expand_params(event: &Event) -> Result<Vec<(TokenStream, TokenStream)>> {
|
||||||
|
event
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, input)| {
|
||||||
|
// NOTE: Events can contain nameless values.
|
||||||
|
let name = util::expand_input_name(i, &input.name);
|
||||||
|
let ty = expand_input_type(&input)?;
|
||||||
|
|
||||||
|
Ok((name, ty))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands an event data structure from its name-type parameter pairs. Returns
|
||||||
|
/// a tuple with the type definition (i.e. the struct declaration) and
|
||||||
|
/// construction (i.e. code for creating an instance of the event data).
|
||||||
|
fn expand_data_struct(
|
||||||
|
name: &TokenStream,
|
||||||
|
params: &[(TokenStream, TokenStream)],
|
||||||
|
) -> (TokenStream, TokenStream) {
|
||||||
|
let fields = params
|
||||||
|
.iter()
|
||||||
|
.map(|(name, ty)| quote! { pub #name: #ty })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let param_names = params
|
||||||
|
.iter()
|
||||||
|
.map(|(name, _)| name)
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let definition = quote! { struct #name { #( #fields, )* } };
|
||||||
|
let construction = quote! { #name { #( #param_names ),* } };
|
||||||
|
|
||||||
|
(definition, construction)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands an event data named tuple from its name-type parameter pairs.
|
||||||
|
/// Returns a tuple with the type definition and construction.
|
||||||
|
fn expand_data_tuple(
|
||||||
|
name: &TokenStream,
|
||||||
|
params: &[(TokenStream, TokenStream)],
|
||||||
|
) -> (TokenStream, TokenStream) {
|
||||||
|
let fields = params
|
||||||
|
.iter()
|
||||||
|
.map(|(_, ty)| quote! { pub #ty })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let param_names = params
|
||||||
|
.iter()
|
||||||
|
.map(|(name, _)| name)
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let definition = quote! { struct #name( #( #fields ),* ); };
|
||||||
|
let construction = quote! { #name( #( #param_names ),* ) };
|
||||||
|
|
||||||
|
(definition, construction)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands an ABI event into filter methods for its indexed parameters.
|
||||||
|
fn expand_builder_topic_filters(event: &Event) -> Result<TokenStream> {
|
||||||
|
let topic_filters = event
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.filter(|input| input.indexed)
|
||||||
|
.enumerate()
|
||||||
|
.map(|(topic_index, input)| expand_builder_topic_filter(topic_index, input))
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
#( #topic_filters )*
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands a event parameter into an event builder filter method for the
|
||||||
|
/// specified topic index.
|
||||||
|
fn expand_builder_topic_filter(topic_index: usize, param: &EventParam) -> Result<TokenStream> {
|
||||||
|
let doc = util::expand_doc(&format!(
|
||||||
|
"Adds a filter for the `{}` event parameter.",
|
||||||
|
param.name,
|
||||||
|
));
|
||||||
|
let topic = util::ident(&format!("topic{}", topic_index));
|
||||||
|
let name = if param.name.is_empty() {
|
||||||
|
topic.clone()
|
||||||
|
} else {
|
||||||
|
util::safe_ident(¶m.name.to_snake_case())
|
||||||
|
};
|
||||||
|
let ty = expand_input_type(¶m)?;
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
#doc
|
||||||
|
pub fn #name(mut self, topic: Topic<#ty>) -> Self {
|
||||||
|
self.0 = (self.0).#topic(topic);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands an ABI event into an identifier for its event data type.
|
||||||
|
fn expand_builder_name(event: &Event) -> TokenStream {
|
||||||
|
let builder_name = util::ident(&format!("{}Builder", &event.name.to_pascal_case()));
|
||||||
|
quote! { #builder_name }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_derives(derives: &[Path]) -> TokenStream {
|
||||||
|
quote! {#(#derives),*}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands an event property type.
|
||||||
|
///
|
||||||
|
/// Note that this is slightly different than an expanding a Solidity type as
|
||||||
|
/// complex types like arrays and strings get emited as hashes when they are
|
||||||
|
/// indexed.
|
||||||
|
fn expand_input_type(input: &EventParam) -> Result<TokenStream> {
|
||||||
|
Ok(match (&input.kind, input.indexed) {
|
||||||
|
(ParamType::Array(..), true)
|
||||||
|
| (ParamType::Bytes, true)
|
||||||
|
| (ParamType::FixedArray(..), true)
|
||||||
|
| (ParamType::String, true)
|
||||||
|
| (ParamType::Tuple(..), true) => {
|
||||||
|
quote! { H256 }
|
||||||
|
}
|
||||||
|
(kind, _) => types::expand(kind)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands a 256-bit `Hash` into a literal representation that can be used with
|
||||||
|
/// quasi-quoting for code generation. We do this to avoid allocating at runtime
|
||||||
|
fn expand_hash(hash: Hash) -> TokenStream {
|
||||||
|
let bytes = hash.as_bytes().iter().copied().map(Literal::u8_unsuffixed);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
H256([#( #bytes ),*])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use ethers_abi::{EventParam, ParamType};
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn expand_empty_filters() {
|
||||||
|
// assert_quote!(expand_filters(&Context::default()).unwrap(), {});
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_transfer_filter() {
|
||||||
|
let event = Event {
|
||||||
|
name: "Transfer".into(),
|
||||||
|
inputs: vec![
|
||||||
|
EventParam {
|
||||||
|
name: "from".into(),
|
||||||
|
kind: ParamType::Address,
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
EventParam {
|
||||||
|
name: "to".into(),
|
||||||
|
kind: ParamType::Address,
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
EventParam {
|
||||||
|
name: "amount".into(),
|
||||||
|
kind: ParamType::Uint(256),
|
||||||
|
indexed: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
anonymous: false,
|
||||||
|
};
|
||||||
|
let signature = expand_hash(event.signature());
|
||||||
|
|
||||||
|
assert_quote!(expand_filter(&event).unwrap(), {
|
||||||
|
/// Generated by `ethcontract`.
|
||||||
|
pub fn transfer(&self) -> self::event_builders::TransferBuilder {
|
||||||
|
self::event_builders::TransferBuilder(
|
||||||
|
self.instance.event(#signature)
|
||||||
|
.expect("generated event filter"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_transfer_builder_topic_filters() {
|
||||||
|
let event = Event {
|
||||||
|
name: "Transfer".into(),
|
||||||
|
inputs: vec![
|
||||||
|
EventParam {
|
||||||
|
name: "from".into(),
|
||||||
|
kind: ParamType::Address,
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
EventParam {
|
||||||
|
name: "to".into(),
|
||||||
|
kind: ParamType::Address,
|
||||||
|
indexed: true,
|
||||||
|
},
|
||||||
|
EventParam {
|
||||||
|
name: "amount".into(),
|
||||||
|
kind: ParamType::Uint(256),
|
||||||
|
indexed: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
anonymous: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
assert_quote!(expand_builder_topic_filters(&event).unwrap(), {
|
||||||
|
#[doc = "Adds a filter for the from event parameter."]
|
||||||
|
pub fn from(mut self, topic: self::ethcontract::Topic<self::ethcontract::Address>) -> Self {
|
||||||
|
self.0 = (self.0).topic0(topic);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Adds a filter for the to event parameter."]
|
||||||
|
pub fn to(mut self, topic: self::ethcontract::Topic<self::ethcontract::Address>) -> Self {
|
||||||
|
self.0 = (self.0).topic1(topic);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_data_struct_value() {
|
||||||
|
let event = Event {
|
||||||
|
name: "Foo".into(),
|
||||||
|
inputs: vec![
|
||||||
|
EventParam {
|
||||||
|
name: "a".into(),
|
||||||
|
kind: ParamType::Bool,
|
||||||
|
indexed: false,
|
||||||
|
},
|
||||||
|
EventParam {
|
||||||
|
name: String::new(),
|
||||||
|
kind: ParamType::Address,
|
||||||
|
indexed: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
anonymous: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = expand_struct_name(&event);
|
||||||
|
let params = expand_params(&event).unwrap();
|
||||||
|
let (definition, construction) = expand_data_struct(&name, ¶ms);
|
||||||
|
|
||||||
|
assert_quote!(definition, {
|
||||||
|
struct Foo {
|
||||||
|
pub a: bool,
|
||||||
|
pub p1: self::ethcontract::Address,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assert_quote!(construction, { Foo { a, p1 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_data_tuple_value() {
|
||||||
|
let event = Event {
|
||||||
|
name: "Foo".into(),
|
||||||
|
inputs: vec![
|
||||||
|
EventParam {
|
||||||
|
name: String::new(),
|
||||||
|
kind: ParamType::Bool,
|
||||||
|
indexed: false,
|
||||||
|
},
|
||||||
|
EventParam {
|
||||||
|
name: String::new(),
|
||||||
|
kind: ParamType::Address,
|
||||||
|
indexed: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
anonymous: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = expand_struct_name(&event);
|
||||||
|
let params = expand_params(&event).unwrap();
|
||||||
|
let (definition, construction) = expand_data_tuple(&name, ¶ms);
|
||||||
|
|
||||||
|
assert_quote!(definition, {
|
||||||
|
struct Foo(pub bool, pub self::ethcontract::Address);
|
||||||
|
});
|
||||||
|
assert_quote!(construction, { Foo(p0, p1) });
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn expand_enum_for_all_events() {
|
||||||
|
// let context = {
|
||||||
|
// let mut context = Context::default();
|
||||||
|
// context.abi.events.insert(
|
||||||
|
// "Foo".into(),
|
||||||
|
// vec![Event {
|
||||||
|
// name: "Foo".into(),
|
||||||
|
// inputs: vec![EventParam {
|
||||||
|
// name: String::new(),
|
||||||
|
// kind: ParamType::Bool,
|
||||||
|
// indexed: false,
|
||||||
|
// }],
|
||||||
|
// anonymous: false,
|
||||||
|
// }],
|
||||||
|
// );
|
||||||
|
// context.abi.events.insert(
|
||||||
|
// "Bar".into(),
|
||||||
|
// vec![Event {
|
||||||
|
// name: "Bar".into(),
|
||||||
|
// inputs: vec![EventParam {
|
||||||
|
// name: String::new(),
|
||||||
|
// kind: ParamType::Address,
|
||||||
|
// indexed: false,
|
||||||
|
// }],
|
||||||
|
// anonymous: true,
|
||||||
|
// }],
|
||||||
|
// );
|
||||||
|
// context.event_derives = ["Asdf", "a::B", "a::b::c::D"]
|
||||||
|
// .iter()
|
||||||
|
// .map(|derive| syn::parse_str::<Path>(derive).unwrap())
|
||||||
|
// .collect();
|
||||||
|
// context
|
||||||
|
// };
|
||||||
|
|
||||||
|
// assert_quote!(expand_event_enum(&context), {
|
||||||
|
// /// A contract event.
|
||||||
|
// #[derive(Clone, Debug, Eq, PartialEq, Asdf, a::B, a::b::c::D)]
|
||||||
|
// pub enum Event {
|
||||||
|
// Bar(self::event_data::Bar),
|
||||||
|
// Foo(self::event_data::Foo),
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// fn expand_parse_log_impl_for_all_events() {
|
||||||
|
// let context = {
|
||||||
|
// let mut context = Context::default();
|
||||||
|
// context.abi.events.insert(
|
||||||
|
// "Foo".into(),
|
||||||
|
// vec![Event {
|
||||||
|
// name: "Foo".into(),
|
||||||
|
// inputs: vec![EventParam {
|
||||||
|
// name: String::new(),
|
||||||
|
// kind: ParamType::Bool,
|
||||||
|
// indexed: false,
|
||||||
|
// }],
|
||||||
|
// anonymous: false,
|
||||||
|
// }],
|
||||||
|
// );
|
||||||
|
// context.abi.events.insert(
|
||||||
|
// "Bar".into(),
|
||||||
|
// vec![Event {
|
||||||
|
// name: "Bar".into(),
|
||||||
|
// inputs: vec![EventParam {
|
||||||
|
// name: String::new(),
|
||||||
|
// kind: ParamType::Address,
|
||||||
|
// indexed: false,
|
||||||
|
// }],
|
||||||
|
// anonymous: true,
|
||||||
|
// }],
|
||||||
|
// );
|
||||||
|
// context
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let foo_signature = expand_hash(context.abi.event("Foo").unwrap().signature());
|
||||||
|
// let invalid_data = expand_invalid_data();
|
||||||
|
|
||||||
|
// assert_quote!(expand_event_parse_log(&context), {
|
||||||
|
// impl self::ethcontract::contract::ParseLog for Event {
|
||||||
|
// fn parse_log(
|
||||||
|
// log: self::ethcontract::RawLog,
|
||||||
|
// ) -> Result<Self, self::ethcontract::errors::ExecutionError> {
|
||||||
|
// let standard_event = log.topics
|
||||||
|
// .get(0)
|
||||||
|
// .copied()
|
||||||
|
// .map(|topic| match topic {
|
||||||
|
// #foo_signature => Ok(Event::Foo(
|
||||||
|
// log.clone().decode(
|
||||||
|
// &Contract::artifact()
|
||||||
|
// .abi
|
||||||
|
// .event("Foo")
|
||||||
|
// .expect("generated event decode")
|
||||||
|
// )?
|
||||||
|
// )),
|
||||||
|
// _ => #invalid_data,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if let Some(Ok(data)) = standard_event {
|
||||||
|
// return Ok(data);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Ok(data) = log.clone().decode(
|
||||||
|
// &Contract::artifact()
|
||||||
|
// .abi
|
||||||
|
// .event("Bar")
|
||||||
|
// .expect("generated event decode")
|
||||||
|
// ) {
|
||||||
|
// return Ok(Event::Bar(data));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #invalid_data
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
fn expand_hash_value() {
|
||||||
|
assert_quote!(
|
||||||
|
expand_hash(
|
||||||
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f".parse().unwrap()
|
||||||
|
),
|
||||||
|
{
|
||||||
|
self::ethcontract::H256([
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||||
|
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
|
||||||
|
])
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
use super::{types, util, Context};
|
||||||
|
|
||||||
|
use ethers_abi::{Function, FunctionExt, Param};
|
||||||
|
use ethers_types::Selector;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
|
use inflector::Inflector;
|
||||||
|
use proc_macro2::{Literal, TokenStream};
|
||||||
|
use quote::quote;
|
||||||
|
use syn::Ident;
|
||||||
|
|
||||||
|
/// Expands a context into a method struct containing all the generated bindings
|
||||||
|
/// to the Solidity contract methods.
|
||||||
|
impl Context {
|
||||||
|
pub(crate) fn methods(&self) -> Result<TokenStream> {
|
||||||
|
let mut aliases = self.method_aliases.clone();
|
||||||
|
|
||||||
|
let functions = self
|
||||||
|
.abi
|
||||||
|
.functions()
|
||||||
|
.map(|function| {
|
||||||
|
let signature = function.abi_signature();
|
||||||
|
expand_function(function, aliases.remove(&signature))
|
||||||
|
.with_context(|| format!("error expanding function '{}'", signature))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
Ok(quote! { #( #functions )* })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn expand_function(function: &Function, alias: Option<Ident>) -> Result<TokenStream> {
|
||||||
|
let name = alias.unwrap_or_else(|| util::safe_ident(&function.name.to_snake_case()));
|
||||||
|
let selector = expand_selector(function.selector());
|
||||||
|
|
||||||
|
let input = expand_inputs(&function.inputs)?;
|
||||||
|
|
||||||
|
let outputs = expand_fn_outputs(&function.outputs)?;
|
||||||
|
|
||||||
|
let result = if function.constant {
|
||||||
|
quote! { Sender<'a, S, P, #outputs> }
|
||||||
|
} else {
|
||||||
|
quote! { Sender<'a, S, P, H256> }
|
||||||
|
};
|
||||||
|
|
||||||
|
let arg = expand_inputs_call_arg(&function.inputs);
|
||||||
|
let doc = util::expand_doc(&format!("Calls the contract's {} function", function.name));
|
||||||
|
Ok(quote! {
|
||||||
|
|
||||||
|
#doc
|
||||||
|
pub fn #name(&self #input) -> #result {
|
||||||
|
self.0.method_hash(#selector, #arg)
|
||||||
|
.expect("method not found (this should never happen)")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// converts the function params to name/type pairs
|
||||||
|
pub(crate) fn expand_inputs(inputs: &[Param]) -> Result<TokenStream> {
|
||||||
|
let params = inputs
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, param)| {
|
||||||
|
let name = util::expand_input_name(i, ¶m.name);
|
||||||
|
let kind = types::expand(¶m.kind)?;
|
||||||
|
Ok(quote! { #name: #kind })
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
Ok(quote! { #( , #params )* })
|
||||||
|
}
|
||||||
|
|
||||||
|
// packs the argument in a tuple to be used for the contract call
|
||||||
|
pub(crate) fn expand_inputs_call_arg(inputs: &[Param]) -> TokenStream {
|
||||||
|
let names = inputs
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, param)| util::expand_input_name(i, ¶m.name));
|
||||||
|
quote! { ( #( #names ,)* ) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_fn_outputs(outputs: &[Param]) -> Result<TokenStream> {
|
||||||
|
match outputs.len() {
|
||||||
|
0 => Ok(quote! { () }),
|
||||||
|
1 => types::expand(&outputs[0].kind),
|
||||||
|
_ => {
|
||||||
|
let types = outputs
|
||||||
|
.iter()
|
||||||
|
.map(|param| types::expand(¶m.kind))
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
Ok(quote! { (#( #types ),*) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_selector(selector: Selector) -> TokenStream {
|
||||||
|
let bytes = selector.iter().copied().map(Literal::u8_unsuffixed);
|
||||||
|
quote! { [#( #bytes ),*] }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use ethers_abi::ParamType;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_inputs_empty() {
|
||||||
|
assert_quote!(expand_inputs(&[]).unwrap().to_string(), {},);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_inputs_() {
|
||||||
|
assert_quote!(
|
||||||
|
expand_inputs(
|
||||||
|
|
||||||
|
&[
|
||||||
|
Param {
|
||||||
|
name: "a".to_string(),
|
||||||
|
kind: ParamType::Bool,
|
||||||
|
},
|
||||||
|
Param {
|
||||||
|
name: "b".to_string(),
|
||||||
|
kind: ParamType::Address,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
{ , a: bool, b: self::ethcontract::Address },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_fn_outputs_empty() {
|
||||||
|
assert_quote!(expand_fn_outputs(&[],).unwrap(), {
|
||||||
|
self::ethcontract::Void
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_fn_outputs_single() {
|
||||||
|
assert_quote!(
|
||||||
|
expand_fn_outputs(&[Param {
|
||||||
|
name: "a".to_string(),
|
||||||
|
kind: ParamType::Bool,
|
||||||
|
}])
|
||||||
|
.unwrap(),
|
||||||
|
{ bool },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_fn_outputs_muliple() {
|
||||||
|
assert_quote!(
|
||||||
|
expand_fn_outputs(&[
|
||||||
|
Param {
|
||||||
|
name: "a".to_string(),
|
||||||
|
kind: ParamType::Bool,
|
||||||
|
},
|
||||||
|
Param {
|
||||||
|
name: "b".to_string(),
|
||||||
|
kind: ParamType::Address,
|
||||||
|
},
|
||||||
|
],)
|
||||||
|
.unwrap(),
|
||||||
|
{ (bool, self::ethcontract::Address) },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use ethcontract_common::abi::ParamType;
|
use ethers_abi::ParamType;
|
||||||
use proc_macro2::{Literal, TokenStream};
|
use proc_macro2::{Literal, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
|
pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
|
||||||
match kind {
|
match kind {
|
||||||
ParamType::Address => Ok(quote! { self::ethcontract::Address }),
|
ParamType::Address => Ok(quote! { Address }),
|
||||||
ParamType::Bytes => Ok(quote! { Vec<u8> }),
|
ParamType::Bytes => Ok(quote! { Vec<u8> }),
|
||||||
ParamType::Int(n) => match n / 8 {
|
ParamType::Int(n) => match n / 8 {
|
||||||
1 => Ok(quote! { i8 }),
|
1 => Ok(quote! { i8 }),
|
||||||
|
@ -13,7 +13,7 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
|
||||||
3..=4 => Ok(quote! { i32 }),
|
3..=4 => Ok(quote! { i32 }),
|
||||||
5..=8 => Ok(quote! { i64 }),
|
5..=8 => Ok(quote! { i64 }),
|
||||||
9..=16 => Ok(quote! { i128 }),
|
9..=16 => Ok(quote! { i128 }),
|
||||||
17..=32 => Ok(quote! { self::ethcontract::I256 }),
|
17..=32 => Ok(quote! { I256 }),
|
||||||
_ => Err(anyhow!("unsupported solidity type int{}", n)),
|
_ => Err(anyhow!("unsupported solidity type int{}", n)),
|
||||||
},
|
},
|
||||||
ParamType::Uint(n) => match n / 8 {
|
ParamType::Uint(n) => match n / 8 {
|
||||||
|
@ -22,7 +22,7 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
|
||||||
3..=4 => Ok(quote! { u32 }),
|
3..=4 => Ok(quote! { u32 }),
|
||||||
5..=8 => Ok(quote! { u64 }),
|
5..=8 => Ok(quote! { u64 }),
|
||||||
9..=16 => Ok(quote! { u128 }),
|
9..=16 => Ok(quote! { u128 }),
|
||||||
17..=32 => Ok(quote! { self::ethcontract::U256 }),
|
17..=32 => Ok(quote! { U256 }),
|
||||||
_ => Err(anyhow!("unsupported solidity type uint{}", n)),
|
_ => Err(anyhow!("unsupported solidity type uint{}", n)),
|
||||||
},
|
},
|
||||||
ParamType::Bool => Ok(quote! { bool }),
|
ParamType::Bool => Ok(quote! { bool }),
|
||||||
|
@ -43,6 +43,7 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
|
||||||
let size = Literal::usize_unsuffixed(*n);
|
let size = Literal::usize_unsuffixed(*n);
|
||||||
Ok(quote! { [#inner; #size] })
|
Ok(quote! { [#inner; #size] })
|
||||||
}
|
}
|
||||||
|
// TODO: Implement abiencoder v2
|
||||||
ParamType::Tuple(_) => Err(anyhow!("ABIEncoderV2 is currently not supported")),
|
ParamType::Tuple(_) => Err(anyhow!("ABIEncoderV2 is currently not supported")),
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
#![allow(unused_imports)]
|
||||||
#![deny(missing_docs, unsafe_code)]
|
#![deny(missing_docs, unsafe_code)]
|
||||||
|
|
||||||
//! Crate for generating type-safe bindings to Ethereum smart contracts. This
|
//! Module for generating type-safe bindings to Ethereum smart contracts. This
|
||||||
//! crate is intended to be used either indirectly with the `ethcontract`
|
//! module is intended to be used either indirectly with the `abigen` procedural
|
||||||
//! crate's `contract` procedural macro or directly from a build script.
|
//! macro or directly from a build script / CLI
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
@ -11,14 +13,17 @@
|
||||||
mod test_macros;
|
mod test_macros;
|
||||||
|
|
||||||
mod contract;
|
mod contract;
|
||||||
|
use contract::Context;
|
||||||
|
|
||||||
mod rustfmt;
|
mod rustfmt;
|
||||||
mod source;
|
mod source;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub use crate::source::Source;
|
pub use ethers_types::Address;
|
||||||
pub use crate::util::parse_address;
|
pub use source::Source;
|
||||||
|
pub use util::parse_address;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
pub use ethcontract_common::Address;
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
@ -28,22 +33,26 @@ use std::path::Path;
|
||||||
/// Internal global arguments passed to the generators for each individual
|
/// Internal global arguments passed to the generators for each individual
|
||||||
/// component that control expansion.
|
/// component that control expansion.
|
||||||
pub(crate) struct Args {
|
pub(crate) struct Args {
|
||||||
/// The source of the truffle artifact JSON for the contract whose bindings
|
/// The source of the ABI JSON for the contract whose bindings
|
||||||
/// are being generated.
|
/// are being generated.
|
||||||
artifact_source: Source,
|
abi_source: Source,
|
||||||
|
|
||||||
|
/// Override the contract name to use for the generated type.
|
||||||
|
contract_name: String,
|
||||||
|
|
||||||
/// The runtime crate name to use.
|
/// The runtime crate name to use.
|
||||||
runtime_crate_name: String,
|
runtime_crate_name: String,
|
||||||
|
|
||||||
/// The visibility modifier to use for the generated module and contract
|
/// The visibility modifier to use for the generated module and contract
|
||||||
/// re-export.
|
/// re-export.
|
||||||
visibility_modifier: Option<String>,
|
visibility_modifier: Option<String>,
|
||||||
|
|
||||||
/// Override the contract module name that contains the generated code.
|
/// Override the contract module name that contains the generated code.
|
||||||
contract_mod_override: Option<String>,
|
contract_mod_override: Option<String>,
|
||||||
/// Override the contract name to use for the generated type.
|
|
||||||
contract_name_override: Option<String>,
|
|
||||||
/// Manually specified deployed contract addresses.
|
|
||||||
deployments: HashMap<u32, Address>,
|
|
||||||
/// Manually specified contract method aliases.
|
/// Manually specified contract method aliases.
|
||||||
method_aliases: HashMap<String, String>,
|
method_aliases: HashMap<String, String>,
|
||||||
|
|
||||||
/// Derives added to event structs and enums.
|
/// Derives added to event structs and enums.
|
||||||
event_derives: Vec<String>,
|
event_derives: Vec<String>,
|
||||||
}
|
}
|
||||||
|
@ -51,14 +60,14 @@ pub(crate) struct Args {
|
||||||
impl Args {
|
impl Args {
|
||||||
/// Creates a new builder given the path to a contract's truffle artifact
|
/// Creates a new builder given the path to a contract's truffle artifact
|
||||||
/// JSON file.
|
/// JSON file.
|
||||||
pub fn new(source: Source) -> Self {
|
pub fn new(contract_name: &str, abi_source: Source) -> Self {
|
||||||
Args {
|
Args {
|
||||||
artifact_source: source,
|
abi_source,
|
||||||
runtime_crate_name: "ethcontract".to_owned(),
|
contract_name: contract_name.to_owned(),
|
||||||
|
|
||||||
|
runtime_crate_name: "abigen".to_owned(),
|
||||||
visibility_modifier: None,
|
visibility_modifier: None,
|
||||||
contract_mod_override: None,
|
contract_mod_override: None,
|
||||||
contract_name_override: None,
|
|
||||||
deployments: HashMap::new(),
|
|
||||||
method_aliases: HashMap::new(),
|
method_aliases: HashMap::new(),
|
||||||
event_derives: Vec::new(),
|
event_derives: Vec::new(),
|
||||||
}
|
}
|
||||||
|
@ -88,35 +97,39 @@ pub struct Builder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
/// Creates a new builder given the path to a contract's truffle artifact
|
/// Creates a new builder given the contract's ABI JSON string
|
||||||
/// JSON file.
|
pub fn from_str(name: &str, abi: &str) -> Self {
|
||||||
pub fn new<P>(artifact_path: P) -> Self
|
Builder::source(name, Source::String(abi.to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new builder given the path to a contract's ABI file
|
||||||
|
pub fn new<P>(name: &str, path: P) -> Self
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
Builder::with_source(Source::local(artifact_path))
|
Builder::source(name, Source::local(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new builder from a source URL.
|
/// Creates a new builder from a source URL.
|
||||||
pub fn from_source_url<S>(source_url: S) -> Result<Self>
|
pub fn from_url<S>(name: &str, url: S) -> Result<Self>
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
{
|
{
|
||||||
let source = Source::parse(source_url)?;
|
let source = Source::parse(url)?;
|
||||||
Ok(Builder::with_source(source))
|
Ok(Builder::source(name, source))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new builder with the given artifact JSON source.
|
/// Creates a new builder with the given ABI JSON source.
|
||||||
pub fn with_source(source: Source) -> Self {
|
pub fn source(name: &str, source: Source) -> Self {
|
||||||
Builder {
|
Builder {
|
||||||
args: Args::new(source),
|
args: Args::new(name, source),
|
||||||
options: SerializationOptions::default(),
|
options: SerializationOptions::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the crate name for the runtime crate. This setting is usually only
|
/// Sets the crate name for the runtime crate. This setting is usually only
|
||||||
/// needed if the crate was renamed in the Cargo manifest.
|
/// needed if the crate was renamed in the Cargo manifest.
|
||||||
pub fn with_runtime_crate_name<S>(mut self, name: S) -> Self
|
pub fn runtime_crate_name<S>(mut self, name: S) -> Self
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
|
@ -126,7 +139,7 @@ impl Builder {
|
||||||
|
|
||||||
/// Sets an optional visibility modifier for the generated module and
|
/// Sets an optional visibility modifier for the generated module and
|
||||||
/// contract re-export.
|
/// contract re-export.
|
||||||
pub fn with_visibility_modifier<S>(mut self, vis: Option<S>) -> Self
|
pub fn visibility_modifier<S>(mut self, vis: Option<S>) -> Self
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
|
@ -135,7 +148,7 @@ impl Builder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the optional contract module name override.
|
/// Sets the optional contract module name override.
|
||||||
pub fn with_contract_mod_override<S>(mut self, name: Option<S>) -> Self
|
pub fn contract_mod_override<S>(mut self, name: Option<S>) -> Self
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
|
@ -143,48 +156,6 @@ impl Builder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the optional contract name override. This setting is needed when
|
|
||||||
/// using a artifact JSON source that does not provide a contract name such
|
|
||||||
/// as Etherscan.
|
|
||||||
pub fn with_contract_name_override<S>(mut self, name: Option<S>) -> Self
|
|
||||||
where
|
|
||||||
S: Into<String>,
|
|
||||||
{
|
|
||||||
self.args.contract_name_override = name.map(S::into);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Manually adds specifies the deployed address of a contract for a given
|
|
||||||
/// network. Note that manually specified deployments take precedence over
|
|
||||||
/// deployments in the Truffle artifact (in the `networks` property of the
|
|
||||||
/// artifact).
|
|
||||||
///
|
|
||||||
/// This is useful for integration test scenarios where the address of a
|
|
||||||
/// contract on the test node is deterministic (for example using
|
|
||||||
/// `ganache-cli -d`) but the contract address is not part of the Truffle
|
|
||||||
/// artifact; or to override a deployment included in a Truffle artifact.
|
|
||||||
pub fn add_deployment(mut self, network_id: u32, address: Address) -> Self {
|
|
||||||
self.args.deployments.insert(network_id, address);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Manually adds specifies the deployed address as a string of a contract
|
|
||||||
/// for a given network. See `Builder::add_deployment` for more information.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// This method panics if the specified address string is invalid. See
|
|
||||||
/// `parse_address` for more information on the address string format.
|
|
||||||
pub fn add_deployment_str<S>(self, network_id: u32, address: S) -> Self
|
|
||||||
where
|
|
||||||
S: AsRef<str>,
|
|
||||||
{
|
|
||||||
self.add_deployment(
|
|
||||||
network_id,
|
|
||||||
parse_address(address).expect("failed to parse address"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Manually adds a solidity method alias to specify what the method name
|
/// Manually adds a solidity method alias to specify what the method name
|
||||||
/// will be in Rust. For solidity methods without an alias, the snake cased
|
/// will be in Rust. For solidity methods without an alias, the snake cased
|
||||||
/// method name will be used.
|
/// method name will be used.
|
||||||
|
@ -204,7 +175,7 @@ impl Builder {
|
||||||
///
|
///
|
||||||
/// Note that in case `rustfmt` does not exist or produces an error, the
|
/// Note that in case `rustfmt` does not exist or produces an error, the
|
||||||
/// unformatted code will be used.
|
/// unformatted code will be used.
|
||||||
pub fn with_rustfmt(mut self, rustfmt: bool) -> Self {
|
pub fn rustfmt(mut self, rustfmt: bool) -> Self {
|
||||||
self.options.rustfmt = rustfmt;
|
self.options.rustfmt = rustfmt;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -232,7 +203,7 @@ impl Builder {
|
||||||
|
|
||||||
/// Generates the contract bindings.
|
/// Generates the contract bindings.
|
||||||
pub fn generate(self) -> Result<ContractBindings> {
|
pub fn generate(self) -> Result<ContractBindings> {
|
||||||
let tokens = contract::expand(self.args)?;
|
let tokens = Context::expand(self.args)?;
|
||||||
Ok(ContractBindings {
|
Ok(ContractBindings {
|
||||||
tokens,
|
tokens,
|
||||||
options: self.options,
|
options: self.options,
|
|
@ -1,48 +1,54 @@
|
||||||
//! Module implements reading of contract artifacts from various sources.
|
//! Module implements reading of contract artifacts from various sources.
|
||||||
|
use super::util;
|
||||||
|
use ethers_types::Address;
|
||||||
|
|
||||||
use crate::util;
|
|
||||||
use anyhow::{anyhow, Context, Error, Result};
|
use anyhow::{anyhow, Context, Error, Result};
|
||||||
use ethcontract_common::Address;
|
use std::{
|
||||||
use std::borrow::Cow;
|
borrow::Cow,
|
||||||
use std::env;
|
env, fs,
|
||||||
use std::fs;
|
path::{Path, PathBuf},
|
||||||
use std::path::{Path, PathBuf};
|
str::FromStr,
|
||||||
use std::str::FromStr;
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
/// A source of a Truffle artifact JSON.
|
/// A source of a Truffle artifact JSON.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum Source {
|
pub enum Source {
|
||||||
/// A Truffle artifact or ABI located on the local file system.
|
/// A raw ABI string
|
||||||
|
String(String),
|
||||||
|
|
||||||
|
/// An ABI located on the local file system.
|
||||||
Local(PathBuf),
|
Local(PathBuf),
|
||||||
/// A truffle artifact or ABI to be retrieved over HTTP(S).
|
|
||||||
|
/// An ABI to be retrieved over HTTP(S).
|
||||||
Http(Url),
|
Http(Url),
|
||||||
|
|
||||||
/// An address of a mainnet contract that has been verified on Etherscan.io.
|
/// An address of a mainnet contract that has been verified on Etherscan.io.
|
||||||
Etherscan(Address),
|
Etherscan(Address),
|
||||||
|
|
||||||
/// The package identifier of an npm package with a path to a Truffle
|
/// The package identifier of an npm package with a path to a Truffle
|
||||||
/// artifact or ABI to be retrieved from `unpkg.io`.
|
/// artifact or ABI to be retrieved from `unpkg.io`.
|
||||||
Npm(String),
|
Npm(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Source {
|
impl Source {
|
||||||
/// Parses an artifact source from a string.
|
/// Parses an ABI from a source
|
||||||
///
|
///
|
||||||
/// Contract artifacts can be retrieved from the local filesystem or online
|
/// Contract ABIs can be retrieved from the local filesystem or online
|
||||||
/// from `etherscan.io`, this method parses artifact source URLs and accepts
|
/// from `etherscan.io`, this method parses ABI source URLs and accepts
|
||||||
/// the following:
|
/// the following:
|
||||||
/// - `relative/path/to/Contract.json`: a relative path to a truffle
|
/// - `relative/path/to/Contract.json`: a relative path to an ABI JSON file.
|
||||||
/// artifact JSON file. This relative path is rooted in the current
|
/// This relative path is rooted in the current working directory.
|
||||||
/// working directory. To specify the root for relative paths, use
|
/// To specify the root for relative paths, use `Source::with_root`.
|
||||||
/// `Source::with_root`.
|
|
||||||
/// - `/absolute/path/to/Contract.json` or
|
/// - `/absolute/path/to/Contract.json` or
|
||||||
/// `file:///absolute/path/to/Contract.json`: an absolute path or file URL
|
/// `file:///absolute/path/to/Contract.json`: an absolute path or file URL
|
||||||
/// to a truffle artifact JSON file.
|
/// to an ABI JSON file.
|
||||||
/// - `http(s)://...` an HTTP url to a contract ABI or Truffle artifact.
|
/// - `http(s)://...` an HTTP url to a contract ABI.
|
||||||
/// - `etherscan:0xXX..XX` or `https://etherscan.io/address/0xXX..XX`: a
|
/// - `etherscan:0xXX..XX` or `https://etherscan.io/address/0xXX..XX`: a
|
||||||
/// address or URL of a verified contract on Etherscan.
|
/// address or URL of a verified contract on Etherscan.
|
||||||
/// - `npm:@org/package@1.0.0/path/to/contract.json` an npmjs package with
|
/// - `npm:@org/package@1.0.0/path/to/contract.json` an npmjs package with
|
||||||
/// an optional version and path (defaulting to the latest version and
|
/// an optional version and path (defaulting to the latest version and
|
||||||
/// `index.js`). The contract artifact or ABI will be retrieved through
|
/// `index.js`). The contract ABI will be retrieved through
|
||||||
/// `unpkg.io`.
|
/// `unpkg.io`.
|
||||||
pub fn parse<S>(source: S) -> Result<Self>
|
pub fn parse<S>(source: S) -> Result<Self>
|
||||||
where
|
where
|
||||||
|
@ -118,12 +124,13 @@ impl Source {
|
||||||
/// Retrieves the source JSON of the artifact this will either read the JSON
|
/// Retrieves the source JSON of the artifact this will either read the JSON
|
||||||
/// from the file system or retrieve a contract ABI from the network
|
/// from the file system or retrieve a contract ABI from the network
|
||||||
/// dependending on the source type.
|
/// dependending on the source type.
|
||||||
pub fn artifact_json(&self) -> Result<String> {
|
pub fn get(&self) -> Result<String> {
|
||||||
match self {
|
match self {
|
||||||
Source::Local(path) => get_local_contract(path),
|
Source::Local(path) => get_local_contract(path),
|
||||||
Source::Http(url) => get_http_contract(url),
|
Source::Http(url) => get_http_contract(url),
|
||||||
Source::Etherscan(address) => get_etherscan_contract(*address),
|
Source::Etherscan(address) => get_etherscan_contract(*address),
|
||||||
Source::Npm(package) => get_npm_contract(package),
|
Source::Npm(package) => get_npm_contract(package),
|
||||||
|
Source::String(abi) => Ok(abi.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
|
use ethers_types::Address;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use curl::easy::Easy;
|
use curl::easy::Easy;
|
||||||
use ethcontract_common::Address;
|
|
||||||
use inflector::Inflector;
|
use inflector::Inflector;
|
||||||
use proc_macro2::{Ident, Literal, Span, TokenStream};
|
use proc_macro2::{Ident, Literal, Span, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
|
@ -0,0 +1,185 @@
|
||||||
|
pub use erc20token_mod::ERC20Token;
|
||||||
|
mod erc20token_mod {
|
||||||
|
use ethers_contract::{
|
||||||
|
abi::{Abi, Detokenize, InvalidOutputType, Token, Tokenizable},
|
||||||
|
providers::JsonRpcClient,
|
||||||
|
signers::{Client, Signer},
|
||||||
|
types::*,
|
||||||
|
Contract, Lazy,
|
||||||
|
};
|
||||||
|
static ABI: Lazy<Abi> = Lazy::new(|| {
|
||||||
|
serde_json :: from_str ( "[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"name\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"name\":\"symbol\",\"type\":\"string\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"name\":\"decimals\",\"type\":\"uint8\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"spender\",\"type\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"totalSupply\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"from\",\"type\":\"address\"},{\"name\":\"to\",\"type\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"who\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"balance\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"to\",\"type\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"success\",\"type\":\"bool\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"owner\",\"type\":\"address\"},{\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"remaining\",\"type\":\"uint256\"}],\"payable\":false,\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"}]" ) . expect ( "invalid abi" )
|
||||||
|
});
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ERC20Token<'a, S, P>(Contract<'a, S, P>);
|
||||||
|
impl<'a, S, P> std::ops::Deref for ERC20Token<'a, S, P> {
|
||||||
|
type Target = Contract<'a, S, P>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, S: Signer, P: JsonRpcClient> std::fmt::Debug for ERC20Token<'a, S, P> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
f.debug_tuple(stringify!(ERC20Token))
|
||||||
|
.field(&self.address())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, S: Signer, P: JsonRpcClient> ERC20Token<'a, S, P> {
|
||||||
|
#[doc = r" Creates a new contract instance with the specified `ethers`"]
|
||||||
|
#[doc = r" client at the given `Address`. The contract derefs to a `ethers::Contract`"]
|
||||||
|
#[doc = r" object"]
|
||||||
|
pub fn new<T: Into<Address>>(address: T, client: &'a Client<'a, S, P>) -> Self {
|
||||||
|
let contract = Contract::new(client, ABI.clone(), address.into());
|
||||||
|
Self(contract)
|
||||||
|
}
|
||||||
|
#[doc = "Calls the contract's balanceOf function"]
|
||||||
|
pub fn balance_of(&self, who: Address) -> Sender<'a, S, P, U256> {
|
||||||
|
self.0
|
||||||
|
.method([112, 160, 130, 49], (who,))
|
||||||
|
.expect("method not found (this should never happen)")
|
||||||
|
}
|
||||||
|
#[doc = "Calls the contract's transfer function"]
|
||||||
|
pub fn transfer(&self, to: Address, value: U256) -> Sender<'a, S, P, H256> {
|
||||||
|
self.0
|
||||||
|
.method([169, 5, 156, 187], (to, value))
|
||||||
|
.expect("method not found (this should never happen)")
|
||||||
|
}
|
||||||
|
#[doc = "Calls the contract's approve function"]
|
||||||
|
pub fn approve(&self, spender: Address, value: U256) -> Sender<'a, S, P, H256> {
|
||||||
|
self.0
|
||||||
|
.method([9, 94, 167, 179], (spender, value))
|
||||||
|
.expect("method not found (this should never happen)")
|
||||||
|
}
|
||||||
|
#[doc = "Calls the contract's name function"]
|
||||||
|
pub fn name(&self) -> Sender<'a, S, P, String> {
|
||||||
|
self.0
|
||||||
|
.method([6, 253, 222, 3], ())
|
||||||
|
.expect("method not found (this should never happen)")
|
||||||
|
}
|
||||||
|
#[doc = "Calls the contract's decimals function"]
|
||||||
|
pub fn decimals(&self) -> Sender<'a, S, P, u8> {
|
||||||
|
self.0
|
||||||
|
.method([49, 60, 229, 103], ())
|
||||||
|
.expect("method not found (this should never happen)")
|
||||||
|
}
|
||||||
|
#[doc = "Calls the contract's symbol function"]
|
||||||
|
pub fn symbol(&self) -> Sender<'a, S, P, String> {
|
||||||
|
self.0
|
||||||
|
.method([149, 216, 155, 65], ())
|
||||||
|
.expect("method not found (this should never happen)")
|
||||||
|
}
|
||||||
|
#[doc = "Calls the contract's totalSupply function"]
|
||||||
|
pub fn total_supply(&self) -> Sender<'a, S, P, U256> {
|
||||||
|
self.0
|
||||||
|
.method([24, 22, 13, 221], ())
|
||||||
|
.expect("method not found (this should never happen)")
|
||||||
|
}
|
||||||
|
#[doc = "Calls the contract's transferFrom function"]
|
||||||
|
pub fn transfer_from(
|
||||||
|
&self,
|
||||||
|
from: Address,
|
||||||
|
to: Address,
|
||||||
|
value: U256,
|
||||||
|
) -> Sender<'a, S, P, H256> {
|
||||||
|
self.0
|
||||||
|
.method([35, 184, 114, 221], (from, to, value))
|
||||||
|
.expect("method not found (this should never happen)")
|
||||||
|
}
|
||||||
|
#[doc = "Calls the contract's allowance function"]
|
||||||
|
pub fn allowance(&self, owner: Address, spender: Address) -> Sender<'a, S, P, U256> {
|
||||||
|
self.0
|
||||||
|
.method([221, 98, 237, 62], (owner, spender))
|
||||||
|
.expect("method not found (this should never happen)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct Transfer {
|
||||||
|
pub from: Address,
|
||||||
|
pub to: Address,
|
||||||
|
pub value: U256,
|
||||||
|
}
|
||||||
|
impl Transfer {
|
||||||
|
#[doc = r" Retrieves the signature for the event this data corresponds to."]
|
||||||
|
#[doc = r" This signature is the Keccak-256 hash of the ABI signature of"]
|
||||||
|
#[doc = r" this event."]
|
||||||
|
pub const fn signature() -> H256 {
|
||||||
|
H256([
|
||||||
|
221, 242, 82, 173, 27, 226, 200, 155, 105, 194, 176, 104, 252, 55, 141, 170, 149,
|
||||||
|
43, 167, 241, 99, 196, 161, 22, 40, 245, 90, 77, 245, 35, 179, 239,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
#[doc = r" Retrieves the ABI signature for the event this data corresponds"]
|
||||||
|
#[doc = r" to. For this event the value should always be:"]
|
||||||
|
#[doc = r""]
|
||||||
|
#[doc = "`Transfer(address,address,uint256)`"]
|
||||||
|
pub const fn abi_signature() -> &'static str {
|
||||||
|
"Transfer(address,address,uint256)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Detokenize for Transfer {
|
||||||
|
fn from_tokens(tokens: Vec<Token>) -> Result<Self, InvalidOutputType> {
|
||||||
|
if tokens.len() != 3 {
|
||||||
|
return Err(InvalidOutputType(format!(
|
||||||
|
"Expected {} tokens, got {}: {:?}",
|
||||||
|
3,
|
||||||
|
tokens.len(),
|
||||||
|
tokens
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut tokens = tokens.into_iter();
|
||||||
|
let from = Address::from_token(tokens.next().expect("this should never happen"))?;
|
||||||
|
let to = Address::from_token(tokens.next().expect("this should never happen"))?;
|
||||||
|
let value = U256::from_token(tokens.next().expect("this should never happen"))?;
|
||||||
|
Ok(Transfer { from, to, value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct Approval {
|
||||||
|
pub owner: Address,
|
||||||
|
pub spender: Address,
|
||||||
|
pub value: U256,
|
||||||
|
}
|
||||||
|
impl Approval {
|
||||||
|
#[doc = r" Retrieves the signature for the event this data corresponds to."]
|
||||||
|
#[doc = r" This signature is the Keccak-256 hash of the ABI signature of"]
|
||||||
|
#[doc = r" this event."]
|
||||||
|
pub const fn signature() -> H256 {
|
||||||
|
H256([
|
||||||
|
140, 91, 225, 229, 235, 236, 125, 91, 209, 79, 113, 66, 125, 30, 132, 243, 221, 3,
|
||||||
|
20, 192, 247, 178, 41, 30, 91, 32, 10, 200, 199, 195, 185, 37,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
#[doc = r" Retrieves the ABI signature for the event this data corresponds"]
|
||||||
|
#[doc = r" to. For this event the value should always be:"]
|
||||||
|
#[doc = r""]
|
||||||
|
#[doc = "`Approval(address,address,uint256)`"]
|
||||||
|
pub const fn abi_signature() -> &'static str {
|
||||||
|
"Approval(address,address,uint256)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Detokenize for Approval {
|
||||||
|
fn from_tokens(tokens: Vec<Token>) -> Result<Self, InvalidOutputType> {
|
||||||
|
if tokens.len() != 3 {
|
||||||
|
return Err(InvalidOutputType(format!(
|
||||||
|
"Expected {} tokens, got {}: {:?}",
|
||||||
|
3,
|
||||||
|
tokens.len(),
|
||||||
|
tokens
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut tokens = tokens.into_iter();
|
||||||
|
let owner = Address::from_token(tokens.next().expect("this should never happen"))?;
|
||||||
|
let spender = Address::from_token(tokens.next().expect("this should never happen"))?;
|
||||||
|
let value = U256::from_token(tokens.next().expect("this should never happen"))?;
|
||||||
|
Ok(Approval {
|
||||||
|
owner,
|
||||||
|
spender,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "ethers-contract-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nicholas Rodrigues Lordello <nlordell@gmail.com>", "Georgios Konstantopoulos <me@gakonst.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
description = "Proc macro for type-safe bindings generation to Ethereum smart contracts"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ethers-abi = { path = "../../ethers-abi" }
|
||||||
|
ethers-contract-abigen = { path = "../ethers-contract-abigen" }
|
||||||
|
|
||||||
|
serde_json = "1.0.53"
|
||||||
|
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
quote = "1.0"
|
||||||
|
syn = "1.0.12"
|
|
@ -1,132 +1,40 @@
|
||||||
//! Implementation of procedural macro for generating type-safe bindings to an
|
//! Implementation of procedural macro for generating type-safe bindings to an
|
||||||
//! ethereum smart contract.
|
//! ethereum smart contract.
|
||||||
#![deny(missing_docs, unsafe_code)]
|
use crate::spanned::{ParseInner, Spanned};
|
||||||
|
|
||||||
extern crate proc_macro;
|
use ethers_abi::{Function, FunctionExt, Param};
|
||||||
|
use ethers_contract_abigen::Builder;
|
||||||
|
|
||||||
mod spanned;
|
|
||||||
use crate::spanned::{ParseInner, Spanned, parse_address, Address, Builder};
|
|
||||||
|
|
||||||
use ethers::abi::{Function, Param, ParamType, FunctionExt, ParamTypeExt};
|
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
|
||||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
use quote::{quote, ToTokens as _};
|
use quote::{quote, ToTokens as _};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use syn::ext::IdentExt;
|
use syn::ext::IdentExt;
|
||||||
use syn::parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult};
|
use syn::parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult};
|
||||||
use syn::{
|
use syn::{braced, parenthesized, Ident, LitStr, Path, Token, Visibility};
|
||||||
braced, parenthesized, parse_macro_input, Error as SynError, Ident, LitInt, LitStr, Path,
|
|
||||||
Token, Visibility,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Make it accept an inline ABI array
|
pub(crate) fn expand(args: ContractArgs) -> Result<TokenStream2, Box<dyn Error>> {
|
||||||
/// Proc macro to generate type-safe bindings to a contract. This macro accepts
|
|
||||||
/// an Ethereum contract ABIABI or a path. Note that this path is rooted in
|
|
||||||
/// the crate's root `CARGO_MANIFEST_DIR`.
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// ethcontract::contract!("build/contracts/MyContract.json");
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Alternatively, other sources may be used, for full details consult the
|
|
||||||
/// `ethcontract-generate::source` documentation. Some basic examples:
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// // HTTP(S) source
|
|
||||||
/// ethcontract::contract!("https://my.domain.local/path/to/contract.json")
|
|
||||||
/// // Etherscan.io
|
|
||||||
/// ethcontract::contract!("etherscan:0x0001020304050607080910111213141516171819");
|
|
||||||
/// ethcontract::contract!("https://etherscan.io/address/0x0001020304050607080910111213141516171819");
|
|
||||||
/// // npmjs
|
|
||||||
/// ethcontract::contract!("npm:@org/package@1.0.0/path/to/contract.json")
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Note that Etherscan rate-limits requests to their API, to avoid this an
|
|
||||||
/// `ETHERSCAN_API_KEY` environment variable can be set. If it is, it will use
|
|
||||||
/// that API key when retrieving the contract ABI.
|
|
||||||
///
|
|
||||||
/// Currently the proc macro accepts additional parameters to configure some
|
|
||||||
/// aspects of the code generation. Specifically it accepts:
|
|
||||||
/// - `crate`: The name of the `ethcontract` crate. This is useful if the crate
|
|
||||||
/// was renamed in the `Cargo.toml` for whatever reason.
|
|
||||||
/// - `contract`: Override the contract name that is used for the generated
|
|
||||||
/// type. This is required when using sources that do not provide the contract
|
|
||||||
/// name in the artifact JSON such as Etherscan.
|
|
||||||
/// - `mod`: The name of the contract module to place generated code in. Note
|
|
||||||
/// that the root contract type gets re-exported in the context where the
|
|
||||||
/// macro was invoked. This defaults to the contract name converted into snake
|
|
||||||
/// case.
|
|
||||||
/// - `methods`: A list of mappings from method signatures to method names
|
|
||||||
/// allowing methods names to be explicitely set for contract methods. This
|
|
||||||
/// also provides a workaround for generating code for contracts with multiple
|
|
||||||
/// methods with the same name.
|
|
||||||
/// - `event_derives`: A list of additional derives that should be added to
|
|
||||||
/// contract event structs and enums.
|
|
||||||
///
|
|
||||||
/// Additionally, the ABI source can be preceeded by a visibility modifier such
|
|
||||||
/// as `pub` or `pub(crate)`. This visibility modifier is applied to both the
|
|
||||||
/// generated module and contract re-export. If no visibility modifier is
|
|
||||||
/// provided, then none is used for the generated code as well, making the
|
|
||||||
/// module and contract private to the scope where the macro was invoked.
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// ethcontract::contract!(
|
|
||||||
/// pub(crate) "build/contracts/MyContract.json",
|
|
||||||
/// crate = ethcontract_rename,
|
|
||||||
/// mod = my_contract_instance,
|
|
||||||
/// contract = MyContractInstance,
|
|
||||||
/// deployments {
|
|
||||||
/// 4 => "0x000102030405060708090a0b0c0d0e0f10111213",
|
|
||||||
/// 5777 => "0x0123456789012345678901234567890123456789",
|
|
||||||
/// },
|
|
||||||
/// methods {
|
|
||||||
/// myMethod(uint256,bool) as my_renamed_method;
|
|
||||||
/// },
|
|
||||||
/// event_derives (serde::Deserialize, serde::Serialize),
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// See [`ethcontract`](ethcontract) module level documentation for additional
|
|
||||||
/// information.
|
|
||||||
#[proc_macro]
|
|
||||||
pub fn contract(input: TokenStream) -> TokenStream {
|
|
||||||
let args = parse_macro_input!(input as Spanned<ContractArgs>);
|
|
||||||
|
|
||||||
let span = args.span();
|
|
||||||
expand(args.into_inner())
|
|
||||||
.unwrap_or_else(|e| SynError::new(span, format!("{:?}", e)).to_compile_error())
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand(args: ContractArgs) -> Result<TokenStream2, Box<dyn Error>> {
|
|
||||||
Ok(args.into_builder()?.generate()?.into_tokens())
|
Ok(args.into_builder()?.generate()?.into_tokens())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contract procedural macro arguments.
|
/// Contract procedural macro arguments.
|
||||||
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
|
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
|
||||||
struct ContractArgs {
|
pub(crate) struct ContractArgs {
|
||||||
visibility: Option<String>,
|
visibility: Option<String>,
|
||||||
artifact_path: String,
|
name: String,
|
||||||
|
path: String,
|
||||||
parameters: Vec<Parameter>,
|
parameters: Vec<Parameter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContractArgs {
|
impl ContractArgs {
|
||||||
fn into_builder(self) -> Result<Builder, Box<dyn Error>> {
|
fn into_builder(self) -> Result<Builder, Box<dyn Error>> {
|
||||||
let mut builder = Builder::from_source_url(&self.artifact_path)?
|
let mut builder =
|
||||||
.with_visibility_modifier(self.visibility);
|
Builder::from_str(&self.name, &self.path).visibility_modifier(self.visibility);
|
||||||
|
|
||||||
for parameter in self.parameters.into_iter() {
|
for parameter in self.parameters.into_iter() {
|
||||||
builder = match parameter {
|
builder = match parameter {
|
||||||
Parameter::Mod(name) => builder.with_contract_mod_override(Some(name)),
|
Parameter::Mod(name) => builder.contract_mod_override(Some(name)),
|
||||||
Parameter::Contract(name) => builder.with_contract_name_override(Some(name)),
|
Parameter::Crate(name) => builder.runtime_crate_name(name),
|
||||||
Parameter::Crate(name) => builder.with_runtime_crate_name(name),
|
|
||||||
Parameter::Deployments(deployments) => {
|
|
||||||
deployments.into_iter().fold(builder, |builder, d| {
|
|
||||||
builder.add_deployment(d.network_id, d.address)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Parameter::Methods(methods) => methods.into_iter().fold(builder, |builder, m| {
|
Parameter::Methods(methods) => methods.into_iter().fold(builder, |builder, m| {
|
||||||
builder.add_method_alias(m.signature, m.alias)
|
builder.add_method_alias(m.signature, m.alias)
|
||||||
}),
|
}),
|
||||||
|
@ -142,17 +50,24 @@ impl ContractArgs {
|
||||||
|
|
||||||
impl ParseInner for ContractArgs {
|
impl ParseInner for ContractArgs {
|
||||||
fn spanned_parse(input: ParseStream) -> ParseResult<(Span, Self)> {
|
fn spanned_parse(input: ParseStream) -> ParseResult<(Span, Self)> {
|
||||||
|
// read the visibility parameter
|
||||||
let visibility = match input.parse::<Visibility>()? {
|
let visibility = match input.parse::<Visibility>()? {
|
||||||
Visibility::Inherited => None,
|
Visibility::Inherited => None,
|
||||||
token => Some(quote!(#token).to_string()),
|
token => Some(quote!(#token).to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// read the contract name
|
||||||
|
let name = input.parse::<Ident>()?.to_string();
|
||||||
|
|
||||||
|
// skip the comma
|
||||||
|
input.parse::<Token![,]>()?;
|
||||||
|
|
||||||
// TODO(nlordell): Due to limitation with the proc-macro Span API, we
|
// TODO(nlordell): Due to limitation with the proc-macro Span API, we
|
||||||
// can't currently get a path the the file where we were called from;
|
// can't currently get a path the the file where we were called from;
|
||||||
// therefore, the path will always be rooted on the cargo manifest
|
// therefore, the path will always be rooted on the cargo manifest
|
||||||
// directory. Eventually we can use the `Span::source_file` API to
|
// directory. Eventually we can use the `Span::source_file` API to
|
||||||
// have a better experience.
|
// have a better experience.
|
||||||
let (span, artifact_path) = {
|
let (span, path) = {
|
||||||
let literal = input.parse::<LitStr>()?;
|
let literal = input.parse::<LitStr>()?;
|
||||||
(literal.span(), literal.value())
|
(literal.span(), literal.value())
|
||||||
};
|
};
|
||||||
|
@ -160,6 +75,7 @@ impl ParseInner for ContractArgs {
|
||||||
if !input.is_empty() {
|
if !input.is_empty() {
|
||||||
input.parse::<Token![,]>()?;
|
input.parse::<Token![,]>()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let parameters = input
|
let parameters = input
|
||||||
.parse_terminated::<_, Token![,]>(Parameter::parse)?
|
.parse_terminated::<_, Token![,]>(Parameter::parse)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -169,7 +85,8 @@ impl ParseInner for ContractArgs {
|
||||||
span,
|
span,
|
||||||
ContractArgs {
|
ContractArgs {
|
||||||
visibility,
|
visibility,
|
||||||
artifact_path,
|
name,
|
||||||
|
path,
|
||||||
parameters,
|
parameters,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
@ -180,9 +97,7 @@ impl ParseInner for ContractArgs {
|
||||||
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
|
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
|
||||||
enum Parameter {
|
enum Parameter {
|
||||||
Mod(String),
|
Mod(String),
|
||||||
Contract(String),
|
|
||||||
Crate(String),
|
Crate(String),
|
||||||
Deployments(Vec<Deployment>),
|
|
||||||
Methods(Vec<Method>),
|
Methods(Vec<Method>),
|
||||||
EventDerives(Vec<String>),
|
EventDerives(Vec<String>),
|
||||||
}
|
}
|
||||||
|
@ -201,35 +116,6 @@ impl Parse for Parameter {
|
||||||
let name = input.parse::<Ident>()?.to_string();
|
let name = input.parse::<Ident>()?.to_string();
|
||||||
Parameter::Mod(name)
|
Parameter::Mod(name)
|
||||||
}
|
}
|
||||||
"contract" => {
|
|
||||||
input.parse::<Token![=]>()?;
|
|
||||||
let name = input.parse::<Ident>()?.to_string();
|
|
||||||
Parameter::Contract(name)
|
|
||||||
}
|
|
||||||
"deployments" => {
|
|
||||||
let content;
|
|
||||||
braced!(content in input);
|
|
||||||
let deployments = {
|
|
||||||
let parsed =
|
|
||||||
content.parse_terminated::<_, Token![,]>(Spanned::<Deployment>::parse)?;
|
|
||||||
|
|
||||||
let mut deployments = Vec::with_capacity(parsed.len());
|
|
||||||
let mut networks = HashSet::new();
|
|
||||||
for deployment in parsed {
|
|
||||||
if !networks.insert(deployment.network_id) {
|
|
||||||
return Err(ParseError::new(
|
|
||||||
deployment.span(),
|
|
||||||
"duplicate network ID in `ethcontract::contract!` macro invocation",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
deployments.push(deployment.into_inner())
|
|
||||||
}
|
|
||||||
|
|
||||||
deployments
|
|
||||||
};
|
|
||||||
|
|
||||||
Parameter::Deployments(deployments)
|
|
||||||
}
|
|
||||||
"methods" => {
|
"methods" => {
|
||||||
let content;
|
let content;
|
||||||
braced!(content in input);
|
braced!(content in input);
|
||||||
|
@ -283,29 +169,6 @@ impl Parse for Parameter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A manually specified dependency.
|
|
||||||
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
|
|
||||||
struct Deployment {
|
|
||||||
network_id: u32,
|
|
||||||
address: Address,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Deployment {
|
|
||||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
|
||||||
let network_id = input.parse::<LitInt>()?.base10_parse()?;
|
|
||||||
input.parse::<Token![=>]>()?;
|
|
||||||
let address = {
|
|
||||||
let literal = input.parse::<LitStr>()?;
|
|
||||||
parse_address(&literal.value()).map_err(|err| ParseError::new(literal.span(), err))?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Deployment {
|
|
||||||
network_id,
|
|
||||||
address,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An explicitely named contract method.
|
/// An explicitely named contract method.
|
||||||
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
|
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
|
||||||
struct Method {
|
struct Method {
|
||||||
|
@ -324,7 +187,7 @@ impl Parse for Method {
|
||||||
.parse_terminated::<_, Token![,]>(Ident::parse)?
|
.parse_terminated::<_, Token![,]>(Ident::parse)?
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ident| {
|
.map(|ident| {
|
||||||
let kind = ParamType::from_str(&ident.to_string())
|
let kind = serde_json::from_value(serde_json::json!(&ident.to_string()))
|
||||||
.map_err(|err| ParseError::new(ident.span(), err))?;
|
.map_err(|err| ParseError::new(ident.span(), err))?;
|
||||||
Ok(Param {
|
Ok(Param {
|
||||||
name: "".into(),
|
name: "".into(),
|
||||||
|
@ -425,10 +288,6 @@ mod tests {
|
||||||
crate = foobar,
|
crate = foobar,
|
||||||
mod = contract,
|
mod = contract,
|
||||||
contract = Contract,
|
contract = Contract,
|
||||||
deployments {
|
|
||||||
1 => "0x000102030405060708090a0b0c0d0e0f10111213",
|
|
||||||
4 => "0x0123456789012345678901234567890123456789",
|
|
||||||
},
|
|
||||||
methods {
|
methods {
|
||||||
myMethod(uint256, bool) as my_renamed_method;
|
myMethod(uint256, bool) as my_renamed_method;
|
||||||
myOtherMethod() as my_other_renamed_method;
|
myOtherMethod() as my_other_renamed_method;
|
||||||
|
@ -462,17 +321,6 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn duplicate_network_id_error() {
|
|
||||||
contract_args_err!(
|
|
||||||
"artifact.json",
|
|
||||||
deployments {
|
|
||||||
1 => "0x000102030405060708090a0b0c0d0e0f10111213",
|
|
||||||
1 => "0x0123456789012345678901234567890123456789",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn duplicate_method_rename_error() {
|
fn duplicate_method_rename_error() {
|
||||||
contract_args_err!(
|
contract_args_err!(
|
|
@ -0,0 +1,86 @@
|
||||||
|
//! Implementation of procedural macro for generating type-safe bindings to an
|
||||||
|
//! ethereum smart contract.
|
||||||
|
#![deny(missing_docs, unsafe_code)]
|
||||||
|
|
||||||
|
mod spanned;
|
||||||
|
use spanned::Spanned;
|
||||||
|
|
||||||
|
mod abigen;
|
||||||
|
use abigen::{expand, ContractArgs};
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use syn::{parse::Error, parse_macro_input};
|
||||||
|
|
||||||
|
/// Proc macro to generate type-safe bindings to a contract. This macro accepts
|
||||||
|
/// an Ethereum contract ABI or a path. Note that this path is rooted in
|
||||||
|
/// the crate's root `CARGO_MANIFEST_DIR`.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// ethcontract::contract!("build/contracts/MyContract.json");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Alternatively, other sources may be used, for full details consult the
|
||||||
|
/// `ethcontract-generate::source` documentation. Some basic examples:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// // HTTP(S) source
|
||||||
|
/// ethcontract::contract!("https://my.domain.local/path/to/contract.json")
|
||||||
|
/// // Etherscan.io
|
||||||
|
/// ethcontract::contract!("etherscan:0x0001020304050607080910111213141516171819");
|
||||||
|
/// ethcontract::contract!("https://etherscan.io/address/0x0001020304050607080910111213141516171819");
|
||||||
|
/// // npmjs
|
||||||
|
/// ethcontract::contract!("npm:@org/package@1.0.0/path/to/contract.json")
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note that Etherscan rate-limits requests to their API, to avoid this an
|
||||||
|
/// `ETHERSCAN_API_KEY` environment variable can be set. If it is, it will use
|
||||||
|
/// that API key when retrieving the contract ABI.
|
||||||
|
///
|
||||||
|
/// Currently the proc macro accepts additional parameters to configure some
|
||||||
|
/// aspects of the code generation. Specifically it accepts:
|
||||||
|
/// - `crate`: The name of the `ethcontract` crate. This is useful if the crate
|
||||||
|
/// was renamed in the `Cargo.toml` for whatever reason.
|
||||||
|
/// - `contract`: Override the contract name that is used for the generated
|
||||||
|
/// type. This is required when using sources that do not provide the contract
|
||||||
|
/// name in the artifact JSON such as Etherscan.
|
||||||
|
/// - `mod`: The name of the contract module to place generated code in. Note
|
||||||
|
/// that the root contract type gets re-exported in the context where the
|
||||||
|
/// macro was invoked. This defaults to the contract name converted into snake
|
||||||
|
/// case.
|
||||||
|
/// - `methods`: A list of mappings from method signatures to method names
|
||||||
|
/// allowing methods names to be explicitely set for contract methods. This
|
||||||
|
/// also provides a workaround for generating code for contracts with multiple
|
||||||
|
/// methods with the same name.
|
||||||
|
/// - `event_derives`: A list of additional derives that should be added to
|
||||||
|
/// contract event structs and enums.
|
||||||
|
///
|
||||||
|
/// Additionally, the ABI source can be preceeded by a visibility modifier such
|
||||||
|
/// as `pub` or `pub(crate)`. This visibility modifier is applied to both the
|
||||||
|
/// generated module and contract re-export. If no visibility modifier is
|
||||||
|
/// provided, then none is used for the generated code as well, making the
|
||||||
|
/// module and contract private to the scope where the macro was invoked.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// ethcontract::contract!(
|
||||||
|
/// pub(crate) "build/contracts/MyContract.json",
|
||||||
|
/// crate = ethcontract_rename,
|
||||||
|
/// mod = my_contract_instance,
|
||||||
|
/// contract = MyContractInstance,
|
||||||
|
/// methods {
|
||||||
|
/// myMethod(uint256,bool) as my_renamed_method;
|
||||||
|
/// },
|
||||||
|
/// event_derives (serde::Deserialize, serde::Serialize),
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// See [`ethcontract`](ethcontract) module level documentation for additional
|
||||||
|
/// information.
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn abigen(input: TokenStream) -> TokenStream {
|
||||||
|
let args = parse_macro_input!(input as Spanned<ContractArgs>);
|
||||||
|
|
||||||
|
let span = args.span();
|
||||||
|
expand(args.into_inner())
|
||||||
|
.unwrap_or_else(|e| Error::new(span, format!("{:?}", e)).to_compile_error())
|
||||||
|
.into()
|
||||||
|
}
|
|
@ -0,0 +1,279 @@
|
||||||
|
use ethers_abi::{
|
||||||
|
Abi, Detokenize, Error, Event as AbiEvent, EventExt, Function, FunctionExt, RawLog, Tokenize,
|
||||||
|
};
|
||||||
|
use ethers_providers::{JsonRpcClient, Provider};
|
||||||
|
use ethers_signers::{Client, Signer};
|
||||||
|
use ethers_types::{
|
||||||
|
Address, BlockNumber, Filter, Selector, TransactionRequest, ValueOrArray, H256, U256,
|
||||||
|
};
|
||||||
|
|
||||||
|
use rustc_hex::ToHex;
|
||||||
|
use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData};
|
||||||
|
|
||||||
|
use thiserror::Error as ThisError;
|
||||||
|
|
||||||
|
/// Represents a contract instance at an address. Provides methods for
|
||||||
|
/// contract interaction.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Contract<'a, S, P> {
|
||||||
|
client: &'a Client<'a, S, P>,
|
||||||
|
abi: &'a Abi,
|
||||||
|
address: Address,
|
||||||
|
|
||||||
|
/// A mapping from method signature to a name-index pair for accessing
|
||||||
|
/// functions in the contract ABI. This is used to avoid allocation when
|
||||||
|
/// searching for matching functions by signature.
|
||||||
|
// Adapted from: https://github.com/gnosis/ethcontract-rs/blob/master/src/contract.rs
|
||||||
|
methods: HashMap<Selector, (String, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S: Signer, P: JsonRpcClient> Contract<'a, S, P> {
|
||||||
|
/// Creates a new contract from the provided client, abi and address
|
||||||
|
pub fn new(client: &'a Client<'a, S, P>, abi: &'a Abi, address: Address) -> Self {
|
||||||
|
let methods = create_mapping(&abi.functions, |function| function.selector());
|
||||||
|
|
||||||
|
Self {
|
||||||
|
client,
|
||||||
|
abi,
|
||||||
|
address,
|
||||||
|
methods,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a transaction builder for the provided function name. If there are
|
||||||
|
/// multiple functions with the same name due to overloading, consider using
|
||||||
|
/// the `method_hash` method instead, since this will use the first match.
|
||||||
|
pub fn event<'b, D: Detokenize>(&'a self, name: &str) -> Result<Event<'a, 'b, P, D>, Error>
|
||||||
|
where
|
||||||
|
'a: 'b,
|
||||||
|
{
|
||||||
|
// get the event's full name
|
||||||
|
let event = self.abi.event(name)?;
|
||||||
|
Ok(Event {
|
||||||
|
provider: &self.client.provider(),
|
||||||
|
filter: Filter::new().event(&event.abi_signature()),
|
||||||
|
event: &event,
|
||||||
|
datatype: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a transaction builder for the provided function name. If there are
|
||||||
|
/// multiple functions with the same name due to overloading, consider using
|
||||||
|
/// the `method_hash` method instead, since this will use the first match.
|
||||||
|
pub fn method<T: Tokenize, D: Detokenize>(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
args: T,
|
||||||
|
) -> Result<Sender<'a, S, P, D>, Error> {
|
||||||
|
// get the function
|
||||||
|
let function = self.abi.function(name)?;
|
||||||
|
self.method_func(function, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a transaction builder for the selected function signature. This should be
|
||||||
|
/// preferred if there are overloaded functions in your smart contract
|
||||||
|
pub fn method_hash<T: Tokenize, D: Detokenize>(
|
||||||
|
&self,
|
||||||
|
signature: Selector,
|
||||||
|
args: T,
|
||||||
|
) -> Result<Sender<'a, S, P, D>, Error> {
|
||||||
|
let function = self
|
||||||
|
.methods
|
||||||
|
.get(&signature)
|
||||||
|
.map(|(name, index)| &self.abi.functions[name][*index])
|
||||||
|
.ok_or_else(|| Error::InvalidName(signature.to_hex::<String>()))?;
|
||||||
|
self.method_func(function, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn method_func<T: Tokenize, D: Detokenize>(
|
||||||
|
&self,
|
||||||
|
function: &Function,
|
||||||
|
args: T,
|
||||||
|
) -> Result<Sender<'a, S, P, D>, Error> {
|
||||||
|
// create the calldata
|
||||||
|
let data = function.encode_input(&args.into_tokens())?;
|
||||||
|
|
||||||
|
// create the tx object
|
||||||
|
let tx = TransactionRequest {
|
||||||
|
to: Some(self.address),
|
||||||
|
data: Some(data.into()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Sender {
|
||||||
|
tx,
|
||||||
|
client: self.client,
|
||||||
|
block: None,
|
||||||
|
function: function.to_owned(),
|
||||||
|
datatype: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn address(&self) -> &Address {
|
||||||
|
&self.address
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn abi(&self) -> &Abi {
|
||||||
|
&self.abi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Sender<'a, S, P, D> {
|
||||||
|
tx: TransactionRequest,
|
||||||
|
function: Function,
|
||||||
|
client: &'a Client<'a, S, P>,
|
||||||
|
block: Option<BlockNumber>,
|
||||||
|
datatype: PhantomData<D>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S, P, D: Detokenize> Sender<'a, S, P, D> {
|
||||||
|
/// Sets the `from` field in the transaction to the provided value
|
||||||
|
pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
|
||||||
|
self.tx.from = Some(from.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `gas` field in the transaction to the provided value
|
||||||
|
pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
|
||||||
|
self.tx.gas = Some(gas.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `gas_price` field in the transaction to the provided value
|
||||||
|
pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
|
||||||
|
self.tx.gas_price = Some(gas_price.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `value` field in the transaction to the provided value
|
||||||
|
pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
|
||||||
|
self.tx.value = Some(value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ThisError, Debug)]
|
||||||
|
// TODO: Can we get rid of this static?
|
||||||
|
pub enum ContractError<P: JsonRpcClient>
|
||||||
|
where
|
||||||
|
P::Error: 'static,
|
||||||
|
{
|
||||||
|
#[error(transparent)]
|
||||||
|
DecodingError(#[from] ethers_abi::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
DetokenizationError(#[from] ethers_abi::InvalidOutputType),
|
||||||
|
#[error(transparent)]
|
||||||
|
CallError(P::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S: Signer, P: JsonRpcClient, D: Detokenize> Sender<'a, S, P, D>
|
||||||
|
where
|
||||||
|
P::Error: 'static,
|
||||||
|
{
|
||||||
|
pub async fn call(self) -> Result<D, ContractError<P>> {
|
||||||
|
let bytes = self
|
||||||
|
.client
|
||||||
|
.call(self.tx, self.block)
|
||||||
|
.await
|
||||||
|
.map_err(ContractError::CallError)?;
|
||||||
|
|
||||||
|
let tokens = self.function.decode_output(&bytes.0)?;
|
||||||
|
|
||||||
|
let data = D::from_tokens(tokens)?;
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(self) -> Result<H256, P::Error> {
|
||||||
|
self.client.send_transaction(self.tx, self.block).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Event<'a, 'b, P, D> {
|
||||||
|
pub filter: Filter,
|
||||||
|
provider: &'a Provider<P>,
|
||||||
|
event: &'b AbiEvent,
|
||||||
|
datatype: PhantomData<D>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b, P, D: Detokenize> Event<'a, 'b, P, D> {
|
||||||
|
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||||
|
self.filter.from_block = Some(block.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||||
|
self.filter.to_block = Some(block.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn topic0<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
|
||||||
|
self.filter.topics.push(topic.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn topics(mut self, topics: &[ValueOrArray<H256>]) -> Self {
|
||||||
|
self.filter.topics.extend_from_slice(topics);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Can we get rid of the static?
|
||||||
|
impl<'a, 'b, P: JsonRpcClient, D: Detokenize> Event<'a, 'b, P, D>
|
||||||
|
where
|
||||||
|
P::Error: 'static,
|
||||||
|
{
|
||||||
|
pub async fn query(self) -> Result<Vec<D>, ContractError<P>> {
|
||||||
|
// get the logs
|
||||||
|
let logs = self
|
||||||
|
.provider
|
||||||
|
.get_logs(&self.filter)
|
||||||
|
.await
|
||||||
|
.map_err(ContractError::CallError)?;
|
||||||
|
|
||||||
|
let events = logs
|
||||||
|
.into_iter()
|
||||||
|
.map(|log| {
|
||||||
|
// ethabi parses the unindexed and indexed logs together to a
|
||||||
|
// vector of tokens
|
||||||
|
let tokens = self
|
||||||
|
.event
|
||||||
|
.parse_log(RawLog {
|
||||||
|
topics: log.topics,
|
||||||
|
data: log.data.0,
|
||||||
|
})?
|
||||||
|
.params
|
||||||
|
.into_iter()
|
||||||
|
.map(|param| param.value)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// convert the tokens to the requested datatype
|
||||||
|
Ok::<_, ContractError<P>>(D::from_tokens(tokens)?)
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
Ok(events)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Utility function for creating a mapping between a unique signature and a
|
||||||
|
/// name-index pair for accessing contract ABI items.
|
||||||
|
fn create_mapping<T, S, F>(
|
||||||
|
elements: &HashMap<String, Vec<T>>,
|
||||||
|
signature: F,
|
||||||
|
) -> HashMap<S, (String, usize)>
|
||||||
|
where
|
||||||
|
S: Hash + Eq,
|
||||||
|
F: Fn(&T) -> S,
|
||||||
|
{
|
||||||
|
let signature = &signature;
|
||||||
|
elements
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(name, sub_elements)| {
|
||||||
|
sub_elements
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(move |(index, element)| (signature(element), (name.to_owned(), index)))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
|
@ -1,284 +1,15 @@
|
||||||
use ethers_abi::{
|
mod contract;
|
||||||
Abi, Detokenize, Error, Event as AbiEvent, EventExt, Function, FunctionExt, RawLog, Tokenize,
|
pub use contract::*;
|
||||||
};
|
|
||||||
use ethers_providers::{JsonRpcClient, Provider};
|
|
||||||
use ethers_signers::{Client, Signer};
|
|
||||||
use ethers_types::{
|
|
||||||
Address, BlockNumber, Filter, Selector, TransactionRequest, ValueOrArray, H256, U256,
|
|
||||||
};
|
|
||||||
|
|
||||||
use rustc_hex::ToHex;
|
#[cfg(feature = "abigen")]
|
||||||
use std::{collections::HashMap, fmt::Debug, hash::Hash};
|
pub use ethers_contract_abigen::Builder;
|
||||||
use thiserror::Error as ThisError;
|
|
||||||
|
|
||||||
/// Represents a contract instance at an address. Provides methods for
|
#[cfg(feature = "abigen")]
|
||||||
/// contract interaction.
|
pub use ethers_contract_derive::abigen;
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Contract<'a, S, P> {
|
|
||||||
client: &'a Client<'a, S, P>,
|
|
||||||
abi: Abi,
|
|
||||||
address: Address,
|
|
||||||
|
|
||||||
/// A mapping from method signature to a name-index pair for accessing
|
// re-export for convenience
|
||||||
/// functions in the contract ABI. This is used to avoid allocation when
|
pub use ethers_abi as abi;
|
||||||
/// searching for matching functions by signature.
|
pub use ethers_providers as providers;
|
||||||
// Adapted from: https://github.com/gnosis/ethcontract-rs/blob/master/src/contract.rs
|
pub use ethers_signers as signers;
|
||||||
methods: HashMap<Selector, (String, usize)>,
|
pub use ethers_types as types;
|
||||||
}
|
pub use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
impl<'a, S: Signer, P: JsonRpcClient> Contract<'a, S, P> {
|
|
||||||
/// Creates a new contract from the provided client, abi and address
|
|
||||||
pub fn new(client: &'a Client<'a, S, P>, abi: Abi, address: Address) -> Self {
|
|
||||||
let methods = create_mapping(&abi.functions, |function| function.selector());
|
|
||||||
|
|
||||||
Self {
|
|
||||||
client,
|
|
||||||
abi,
|
|
||||||
address,
|
|
||||||
methods,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a transaction builder for the provided function name. If there are
|
|
||||||
/// multiple functions with the same name due to overloading, consider using
|
|
||||||
/// the `method_hash` method instead, since this will use the first match.
|
|
||||||
pub fn event<'b, D: Detokenize>(&'a self, name: &str) -> Result<Event<'a, 'b, P, D>, Error>
|
|
||||||
where
|
|
||||||
'a: 'b,
|
|
||||||
{
|
|
||||||
// get the event's full name
|
|
||||||
let event = self.abi.event(name)?;
|
|
||||||
Ok(Event {
|
|
||||||
provider: &self.client.provider(),
|
|
||||||
filter: Filter::new().event(&event.abi_signature()),
|
|
||||||
event: &event,
|
|
||||||
datatype: PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a transaction builder for the provided function name. If there are
|
|
||||||
/// multiple functions with the same name due to overloading, consider using
|
|
||||||
/// the `method_hash` method instead, since this will use the first match.
|
|
||||||
pub fn method<T: Tokenize, D: Detokenize>(
|
|
||||||
&self,
|
|
||||||
name: &str,
|
|
||||||
args: Option<T>,
|
|
||||||
) -> Result<Sender<'a, S, P, D>, Error> {
|
|
||||||
// get the function
|
|
||||||
let function = self.abi.function(name)?;
|
|
||||||
self.method_func(function, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a transaction builder for the selected function signature. This should be
|
|
||||||
/// preferred if there are overloaded functions in your smart contract
|
|
||||||
pub fn method_hash<T: Tokenize, D: Detokenize>(
|
|
||||||
&self,
|
|
||||||
signature: Selector,
|
|
||||||
args: Option<T>,
|
|
||||||
) -> Result<Sender<'a, S, P, D>, Error> {
|
|
||||||
let function = self
|
|
||||||
.methods
|
|
||||||
.get(&signature)
|
|
||||||
.map(|(name, index)| &self.abi.functions[name][*index])
|
|
||||||
.ok_or_else(|| Error::InvalidName(signature.to_hex::<String>()))?;
|
|
||||||
self.method_func(function, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn method_func<T: Tokenize, D: Detokenize>(
|
|
||||||
&self,
|
|
||||||
function: &Function,
|
|
||||||
args: Option<T>,
|
|
||||||
) -> Result<Sender<'a, S, P, D>, Error> {
|
|
||||||
// create the calldata
|
|
||||||
let data = if let Some(args) = args {
|
|
||||||
function.encode_input(&args.into_tokens())?
|
|
||||||
} else {
|
|
||||||
function.selector().to_vec()
|
|
||||||
};
|
|
||||||
|
|
||||||
// create the tx object
|
|
||||||
let tx = TransactionRequest {
|
|
||||||
to: Some(self.address),
|
|
||||||
data: Some(data.into()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Sender {
|
|
||||||
tx,
|
|
||||||
client: self.client,
|
|
||||||
block: None,
|
|
||||||
function: function.to_owned(),
|
|
||||||
datatype: PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn address(&self) -> &Address {
|
|
||||||
&self.address
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn abi(&self) -> &Abi {
|
|
||||||
&self.abi
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Sender<'a, S, P, D> {
|
|
||||||
tx: TransactionRequest,
|
|
||||||
function: Function,
|
|
||||||
client: &'a Client<'a, S, P>,
|
|
||||||
block: Option<BlockNumber>,
|
|
||||||
datatype: PhantomData<D>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, S, P, D: Detokenize> Sender<'a, S, P, D> {
|
|
||||||
/// Sets the `from` field in the transaction to the provided value
|
|
||||||
pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
|
|
||||||
self.tx.from = Some(from.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the `gas` field in the transaction to the provided value
|
|
||||||
pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
|
|
||||||
self.tx.gas = Some(gas.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the `gas_price` field in the transaction to the provided value
|
|
||||||
pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
|
|
||||||
self.tx.gas_price = Some(gas_price.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the `value` field in the transaction to the provided value
|
|
||||||
pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
|
|
||||||
self.tx.value = Some(value.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(ThisError, Debug)]
|
|
||||||
// TODO: Can we get rid of this static?
|
|
||||||
pub enum ContractError<P: JsonRpcClient>
|
|
||||||
where
|
|
||||||
P::Error: 'static,
|
|
||||||
{
|
|
||||||
#[error(transparent)]
|
|
||||||
DecodingError(#[from] ethers_abi::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
DetokenizationError(#[from] ethers_abi::InvalidOutputType),
|
|
||||||
#[error(transparent)]
|
|
||||||
CallError(P::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, S: Signer, P: JsonRpcClient, D: Detokenize> Sender<'a, S, P, D>
|
|
||||||
where
|
|
||||||
P::Error: 'static,
|
|
||||||
{
|
|
||||||
pub async fn call(self) -> Result<D, ContractError<P>> {
|
|
||||||
let bytes = self
|
|
||||||
.client
|
|
||||||
.call(self.tx, self.block)
|
|
||||||
.await
|
|
||||||
.map_err(ContractError::CallError)?;
|
|
||||||
|
|
||||||
let tokens = self.function.decode_output(&bytes.0)?;
|
|
||||||
|
|
||||||
let data = D::from_tokens(tokens)?;
|
|
||||||
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send(self) -> Result<H256, P::Error> {
|
|
||||||
self.client.send_transaction(self.tx, self.block).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Event<'a, 'b, P, D> {
|
|
||||||
filter: Filter,
|
|
||||||
provider: &'a Provider<P>,
|
|
||||||
event: &'b AbiEvent,
|
|
||||||
datatype: PhantomData<D>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b, P, D: Detokenize> Event<'a, 'b, P, D> {
|
|
||||||
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
|
||||||
self.filter.from_block = Some(block.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
|
||||||
self.filter.to_block = Some(block.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn topic<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
|
|
||||||
self.filter.topics.push(topic.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn topics(mut self, topics: &[ValueOrArray<H256>]) -> Self {
|
|
||||||
self.filter.topics.extend_from_slice(topics);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Can we get rid of the static?
|
|
||||||
impl<'a, 'b, P: JsonRpcClient, D: Detokenize> Event<'a, 'b, P, D>
|
|
||||||
where
|
|
||||||
P::Error: 'static,
|
|
||||||
{
|
|
||||||
pub async fn query(self) -> Result<Vec<D>, ContractError<P>> {
|
|
||||||
// get the logs
|
|
||||||
let logs = self
|
|
||||||
.provider
|
|
||||||
.get_logs(&self.filter)
|
|
||||||
.await
|
|
||||||
.map_err(ContractError::CallError)?;
|
|
||||||
|
|
||||||
let events = logs
|
|
||||||
.into_iter()
|
|
||||||
.map(|log| {
|
|
||||||
// ethabi parses the unindexed and indexed logs together to a
|
|
||||||
// vector of tokens
|
|
||||||
let tokens = self
|
|
||||||
.event
|
|
||||||
.parse_log(RawLog {
|
|
||||||
topics: log.topics,
|
|
||||||
data: log.data.0,
|
|
||||||
})?
|
|
||||||
.params
|
|
||||||
.into_iter()
|
|
||||||
.map(|param| param.value)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// convert the tokens to the requested datatype
|
|
||||||
Ok::<_, ContractError<P>>(D::from_tokens(tokens)?)
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
|
|
||||||
Ok(events)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Utility function for creating a mapping between a unique signature and a
|
|
||||||
/// name-index pair for accessing contract ABI items.
|
|
||||||
fn create_mapping<T, S, F>(
|
|
||||||
elements: &HashMap<String, Vec<T>>,
|
|
||||||
signature: F,
|
|
||||||
) -> HashMap<S, (String, usize)>
|
|
||||||
where
|
|
||||||
S: Hash + Eq,
|
|
||||||
F: Fn(&T) -> S,
|
|
||||||
{
|
|
||||||
let signature = &signature;
|
|
||||||
elements
|
|
||||||
.iter()
|
|
||||||
.flat_map(|(name, sub_elements)| {
|
|
||||||
sub_elements
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(move |(index, element)| (signature(element), (name.to_owned(), index)))
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,178 +0,0 @@
|
||||||
#![deny(missing_docs)]
|
|
||||||
|
|
||||||
//! Crate for generating type-safe bindings to Ethereum smart contracts. This
|
|
||||||
//! crate is intended to be used either indirectly with the `ethcontract`
|
|
||||||
//! crate's `contract` procedural macro or directly from a build script.
|
|
||||||
|
|
||||||
mod common;
|
|
||||||
mod deployment;
|
|
||||||
mod events;
|
|
||||||
mod methods;
|
|
||||||
mod types;
|
|
||||||
|
|
||||||
use crate::util;
|
|
||||||
use crate::Args;
|
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
|
||||||
use ethcontract_common::{Address, Artifact};
|
|
||||||
use inflector::Inflector;
|
|
||||||
use proc_macro2::{Ident, Literal, TokenStream};
|
|
||||||
use quote::quote;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use syn::{Path, Visibility};
|
|
||||||
|
|
||||||
/// Internal shared context for generating smart contract bindings.
|
|
||||||
pub(crate) struct Context {
|
|
||||||
/// The artifact JSON as string literal.
|
|
||||||
artifact_json: Literal,
|
|
||||||
/// The parsed artifact.
|
|
||||||
artifact: Artifact,
|
|
||||||
/// The identifier for the runtime crate. Usually this is `ethcontract` but
|
|
||||||
/// it can be different if the crate was renamed in the Cargo manifest for
|
|
||||||
/// example.
|
|
||||||
runtime_crate: Ident,
|
|
||||||
/// The visibility for the generated module and re-exported contract type.
|
|
||||||
visibility: Visibility,
|
|
||||||
/// The name of the module as an identifier in which to place the contract
|
|
||||||
/// implementation. Note that the main contract type gets re-exported in the
|
|
||||||
/// root.
|
|
||||||
contract_mod: Ident,
|
|
||||||
/// The contract name as an identifier.
|
|
||||||
contract_name: Ident,
|
|
||||||
/// Additional contract deployments.
|
|
||||||
deployments: HashMap<u32, Address>,
|
|
||||||
/// Manually specified method aliases.
|
|
||||||
method_aliases: HashMap<String, Ident>,
|
|
||||||
/// Derives added to event structs and enums.
|
|
||||||
event_derives: Vec<Path>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context {
|
|
||||||
/// Create a context from the code generation arguments.
|
|
||||||
fn from_args(args: Args) -> Result<Self> {
|
|
||||||
let (artifact_json, artifact) = {
|
|
||||||
let artifact_json = args
|
|
||||||
.artifact_source
|
|
||||||
.artifact_json()
|
|
||||||
.context("failed to get artifact JSON")?;
|
|
||||||
|
|
||||||
let artifact = Artifact::from_json(&artifact_json)
|
|
||||||
.with_context(|| format!("invalid artifact JSON '{}'", artifact_json))
|
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to parse artifact from source {:?}",
|
|
||||||
args.artifact_source,
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
(Literal::string(&artifact_json), artifact)
|
|
||||||
};
|
|
||||||
|
|
||||||
let raw_contract_name = if let Some(name) = args.contract_name_override.as_ref() {
|
|
||||||
name
|
|
||||||
} else if !artifact.contract_name.is_empty() {
|
|
||||||
&artifact.contract_name
|
|
||||||
} else {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"contract artifact is missing a name, this can happen when \
|
|
||||||
using a source that does not provide a contract name such as \
|
|
||||||
Etherscan; in this case the contract must be manually \
|
|
||||||
specified"
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let runtime_crate = util::ident(&args.runtime_crate_name);
|
|
||||||
let visibility = match args.visibility_modifier.as_ref() {
|
|
||||||
Some(vis) => syn::parse_str(vis)?,
|
|
||||||
None => Visibility::Inherited,
|
|
||||||
};
|
|
||||||
let contract_mod = if let Some(name) = args.contract_mod_override.as_ref() {
|
|
||||||
util::ident(name)
|
|
||||||
} else {
|
|
||||||
util::ident(&raw_contract_name.to_snake_case())
|
|
||||||
};
|
|
||||||
let contract_name = util::ident(raw_contract_name);
|
|
||||||
|
|
||||||
// NOTE: We only check for duplicate signatures here, since if there are
|
|
||||||
// duplicate aliases, the compiler will produce a warning because a
|
|
||||||
// method will be re-defined.
|
|
||||||
let mut method_aliases = HashMap::new();
|
|
||||||
for (signature, alias) in args.method_aliases.into_iter() {
|
|
||||||
let alias = syn::parse_str(&alias)?;
|
|
||||||
if method_aliases.insert(signature.clone(), alias).is_some() {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"duplicate method signature '{}' in method aliases",
|
|
||||||
signature,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let event_derives = args
|
|
||||||
.event_derives
|
|
||||||
.iter()
|
|
||||||
.map(|derive| syn::parse_str::<Path>(derive))
|
|
||||||
.collect::<Result<Vec<_>, _>>()
|
|
||||||
.context("failed to parse event derives")?;
|
|
||||||
|
|
||||||
Ok(Context {
|
|
||||||
artifact_json,
|
|
||||||
artifact,
|
|
||||||
runtime_crate,
|
|
||||||
visibility,
|
|
||||||
contract_mod,
|
|
||||||
contract_name,
|
|
||||||
deployments: args.deployments,
|
|
||||||
method_aliases,
|
|
||||||
event_derives,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
impl Default for Context {
|
|
||||||
fn default() -> Self {
|
|
||||||
Context {
|
|
||||||
artifact_json: Literal::string("{}"),
|
|
||||||
artifact: Artifact::empty(),
|
|
||||||
runtime_crate: util::ident("ethcontract"),
|
|
||||||
visibility: Visibility::Inherited,
|
|
||||||
contract_mod: util::ident("contract"),
|
|
||||||
contract_name: util::ident("Contract"),
|
|
||||||
deployments: HashMap::new(),
|
|
||||||
method_aliases: HashMap::new(),
|
|
||||||
event_derives: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn expand(args: Args) -> Result<TokenStream> {
|
|
||||||
let cx = Context::from_args(args)?;
|
|
||||||
let contract = expand_contract(&cx).context("error expanding contract from its ABI")?;
|
|
||||||
|
|
||||||
Ok(contract)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand_contract(cx: &Context) -> Result<TokenStream> {
|
|
||||||
let runtime_crate = &cx.runtime_crate;
|
|
||||||
let vis = &cx.visibility;
|
|
||||||
let contract_mod = &cx.contract_mod;
|
|
||||||
let contract_name = &cx.contract_name;
|
|
||||||
|
|
||||||
let common = common::expand(cx);
|
|
||||||
let deployment = deployment::expand(cx)?;
|
|
||||||
let methods = methods::expand(cx)?;
|
|
||||||
let events = events::expand(cx)?;
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#vis mod #contract_mod {
|
|
||||||
#[rustfmt::skip]
|
|
||||||
use #runtime_crate as ethcontract;
|
|
||||||
|
|
||||||
#common
|
|
||||||
#deployment
|
|
||||||
#methods
|
|
||||||
#events
|
|
||||||
}
|
|
||||||
#vis use self::#contract_mod::Contract as #contract_name;
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,200 +0,0 @@
|
||||||
use crate::contract::Context;
|
|
||||||
use crate::util::expand_doc;
|
|
||||||
use ethcontract_common::Address;
|
|
||||||
use proc_macro2::{Literal, TokenStream};
|
|
||||||
use quote::quote;
|
|
||||||
|
|
||||||
pub(crate) fn expand(cx: &Context) -> TokenStream {
|
|
||||||
let artifact_json = &cx.artifact_json;
|
|
||||||
let contract_name = &cx.contract_name;
|
|
||||||
|
|
||||||
let doc_str = cx
|
|
||||||
.artifact
|
|
||||||
.devdoc
|
|
||||||
.details
|
|
||||||
.as_deref()
|
|
||||||
.unwrap_or("Generated by `ethcontract`");
|
|
||||||
let doc = expand_doc(doc_str);
|
|
||||||
|
|
||||||
let deployments = cx.deployments.iter().map(|(network_id, address)| {
|
|
||||||
let network_id = Literal::string(&network_id.to_string());
|
|
||||||
let address = expand_address(*address);
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
artifact.networks.insert(
|
|
||||||
#network_id.to_owned(),
|
|
||||||
self::ethcontract::common::truffle::Network {
|
|
||||||
address: #address,
|
|
||||||
transaction_hash: None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#doc
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Contract {
|
|
||||||
methods: Methods,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Contract {
|
|
||||||
/// Retrieves the truffle artifact used to generate the type safe
|
|
||||||
/// API for this contract.
|
|
||||||
pub fn artifact() -> &'static self::ethcontract::Artifact {
|
|
||||||
use self::ethcontract::private::lazy_static;
|
|
||||||
use self::ethcontract::Artifact;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref ARTIFACT: Artifact = {
|
|
||||||
#[allow(unused_mut)]
|
|
||||||
let mut artifact = Artifact::from_json(#artifact_json)
|
|
||||||
.expect("valid artifact JSON");
|
|
||||||
#( #deployments )*
|
|
||||||
|
|
||||||
artifact
|
|
||||||
};
|
|
||||||
}
|
|
||||||
&ARTIFACT
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new contract instance with the specified `web3`
|
|
||||||
/// provider at the given `Address`.
|
|
||||||
///
|
|
||||||
/// Note that this does not verify that a contract with a maching
|
|
||||||
/// `Abi` is actually deployed at the given address.
|
|
||||||
pub fn at<F, T>(
|
|
||||||
web3: &self::ethcontract::web3::api::Web3<T>,
|
|
||||||
address: self::ethcontract::Address,
|
|
||||||
) -> Self
|
|
||||||
where
|
|
||||||
F: self::ethcontract::web3::futures::Future<
|
|
||||||
Item = self::ethcontract::json::Value,
|
|
||||||
Error = self::ethcontract::web3::Error,
|
|
||||||
> + Send + 'static,
|
|
||||||
T: self::ethcontract::web3::Transport<Out = F> + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
Contract::with_transaction(web3, address, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Creates a new contract instance with the specified `web3` provider with
|
|
||||||
/// the given `Abi` at the given `Address` and an optional transaction hash.
|
|
||||||
/// This hash is used to retrieve contract related information such as the
|
|
||||||
/// creation block (which is useful for fetching all historic events).
|
|
||||||
///
|
|
||||||
/// Note that this does not verify that a contract with a matching `Abi` is
|
|
||||||
/// actually deployed at the given address nor that the transaction hash,
|
|
||||||
/// when provided, is actually for this contract deployment.
|
|
||||||
pub fn with_transaction<F, T>(
|
|
||||||
web3: &self::ethcontract::web3::api::Web3<T>,
|
|
||||||
address: self::ethcontract::Address,
|
|
||||||
transaction_hash: Option<self::ethcontract::H256>,
|
|
||||||
) -> Self
|
|
||||||
where
|
|
||||||
F: self::ethcontract::web3::futures::Future<
|
|
||||||
Item = self::ethcontract::json::Value,
|
|
||||||
Error = self::ethcontract::web3::Error,
|
|
||||||
> + Send + 'static,
|
|
||||||
T: self::ethcontract::web3::Transport<Out = F> + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
use self::ethcontract::Instance;
|
|
||||||
use self::ethcontract::transport::DynTransport;
|
|
||||||
use self::ethcontract::web3::api::Web3;
|
|
||||||
|
|
||||||
let transport = DynTransport::new(web3.transport().clone());
|
|
||||||
let web3 = Web3::new(transport);
|
|
||||||
let abi = Self::artifact().abi.clone();
|
|
||||||
let instance = Instance::with_transaction(web3, abi, address, transaction_hash);
|
|
||||||
|
|
||||||
Contract::from_raw(instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a contract from a raw instance.
|
|
||||||
fn from_raw(instance: self::ethcontract::dyns::DynInstance) -> Self {
|
|
||||||
let methods = Methods { instance };
|
|
||||||
Contract { methods }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the contract address being used by this instance.
|
|
||||||
pub fn address(&self) -> self::ethcontract::Address {
|
|
||||||
self.raw_instance().address()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the hash for the transaction that deployed the contract
|
|
||||||
/// if it is known, `None` otherwise.
|
|
||||||
pub fn transaction_hash(&self) -> Option<self::ethcontract::H256> {
|
|
||||||
self.raw_instance().transaction_hash()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the default method options used by this
|
|
||||||
/// contract.
|
|
||||||
pub fn defaults(&self) -> &self::ethcontract::contract::MethodDefaults {
|
|
||||||
&self.raw_instance().defaults
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mutable reference to the default method options used
|
|
||||||
/// by this contract.
|
|
||||||
pub fn defaults_mut(&mut self) -> &mut self::ethcontract::contract::MethodDefaults {
|
|
||||||
&mut self.raw_instance_mut().defaults
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the raw runtime instance used by this
|
|
||||||
/// contract.
|
|
||||||
pub fn raw_instance(&self) -> &self::ethcontract::dyns::DynInstance {
|
|
||||||
&self.methods.instance
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mutable reference to the raw runtime instance used by
|
|
||||||
/// this contract.
|
|
||||||
fn raw_instance_mut(&mut self) -> &mut self::ethcontract::dyns::DynInstance {
|
|
||||||
&mut self.methods.instance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for Contract {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
f.debug_tuple(stringify!(#contract_name))
|
|
||||||
.field(&self.address())
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands an `Address` into a literal representation that can be used with
|
|
||||||
/// quasi-quoting for code generation.
|
|
||||||
fn expand_address(address: Address) -> TokenStream {
|
|
||||||
let bytes = address
|
|
||||||
.as_bytes()
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.map(Literal::u8_unsuffixed);
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
self::ethcontract::H160([#( #bytes ),*])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[rustfmt::skip]
|
|
||||||
fn expand_address_value() {
|
|
||||||
assert_quote!(
|
|
||||||
expand_address(Address::zero()),
|
|
||||||
{
|
|
||||||
self::ethcontract::H160([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ])
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_quote!(
|
|
||||||
expand_address("000102030405060708090a0b0c0d0e0f10111213".parse().unwrap()),
|
|
||||||
{
|
|
||||||
self::ethcontract::H160([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,167 +0,0 @@
|
||||||
use crate::contract::{methods, Context};
|
|
||||||
use crate::util;
|
|
||||||
use anyhow::{Context as _, Result};
|
|
||||||
use inflector::Inflector;
|
|
||||||
use proc_macro2::{Literal, TokenStream};
|
|
||||||
use quote::quote;
|
|
||||||
|
|
||||||
pub(crate) fn expand(cx: &Context) -> Result<TokenStream> {
|
|
||||||
let deployed = expand_deployed(&cx);
|
|
||||||
let deploy =
|
|
||||||
expand_deploy(&cx).context("error generating contract `deploy` associated function")?;
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
#deployed
|
|
||||||
#deploy
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand_deployed(cx: &Context) -> TokenStream {
|
|
||||||
if cx.artifact.networks.is_empty() && cx.deployments.is_empty() {
|
|
||||||
return quote! {};
|
|
||||||
}
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
impl Contract {
|
|
||||||
/// Locates a deployed contract based on the current network ID
|
|
||||||
/// reported by the `web3` provider.
|
|
||||||
///
|
|
||||||
/// Note that this does not verify that a contract with a maching
|
|
||||||
/// `Abi` is actually deployed at the given address.
|
|
||||||
pub async fn deployed<F, T>(
|
|
||||||
web3: &self::ethcontract::web3::api::Web3<T>,
|
|
||||||
) -> Result<Self, self::ethcontract::errors::DeployError>
|
|
||||||
where
|
|
||||||
F: self::ethcontract::web3::futures::Future<
|
|
||||||
Item = self::ethcontract::json::Value,
|
|
||||||
Error = self::ethcontract::web3::Error
|
|
||||||
> + Send + 'static,
|
|
||||||
T: self::ethcontract::web3::Transport<Out = F> + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
use self::ethcontract::{Instance, Web3};
|
|
||||||
use self::ethcontract::transport::DynTransport;
|
|
||||||
|
|
||||||
let transport = DynTransport::new(web3.transport().clone());
|
|
||||||
let web3 = Web3::new(transport);
|
|
||||||
let instance = Instance::deployed(web3, Contract::artifact().clone()).await?;
|
|
||||||
|
|
||||||
Ok(Contract::from_raw(instance))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand_deploy(cx: &Context) -> Result<TokenStream> {
|
|
||||||
if cx.artifact.bytecode.is_empty() {
|
|
||||||
// do not generate deploy method for contracts that have empty bytecode
|
|
||||||
return Ok(quote! {});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(nlordell): not sure how contructor documentation get generated as I
|
|
||||||
// can't seem to get truffle to output it
|
|
||||||
let doc = util::expand_doc("Generated by `ethcontract`");
|
|
||||||
|
|
||||||
let (input, arg) = match cx.artifact.abi.constructor() {
|
|
||||||
Some(contructor) => (
|
|
||||||
methods::expand_inputs(&contructor.inputs)?,
|
|
||||||
methods::expand_inputs_call_arg(&contructor.inputs),
|
|
||||||
),
|
|
||||||
None => (quote! {}, quote! {()}),
|
|
||||||
};
|
|
||||||
|
|
||||||
let libs: Vec<_> = cx
|
|
||||||
.artifact
|
|
||||||
.bytecode
|
|
||||||
.undefined_libraries()
|
|
||||||
.map(|name| (name, util::safe_ident(&name.to_snake_case())))
|
|
||||||
.collect();
|
|
||||||
let (lib_struct, lib_input, link) = if !libs.is_empty() {
|
|
||||||
let lib_struct = {
|
|
||||||
let lib_struct_fields = libs.iter().map(|(name, field)| {
|
|
||||||
let doc = util::expand_doc(&format!("Address of the `{}` library.", name));
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#doc pub #field: self::ethcontract::Address
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
/// Undefinied libraries in the contract bytecode that are
|
|
||||||
/// required for linking in order to deploy.
|
|
||||||
pub struct Libraries {
|
|
||||||
#( #lib_struct_fields, )*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let link = {
|
|
||||||
let link_libraries = libs.iter().map(|(name, field)| {
|
|
||||||
let name_lit = Literal::string(&name);
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
bytecode.link(#name_lit, libs.#field).expect("valid library");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
let mut bytecode = bytecode;
|
|
||||||
#( #link_libraries )*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
(lib_struct, quote! { , libs: Libraries }, link)
|
|
||||||
} else {
|
|
||||||
Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
#lib_struct
|
|
||||||
|
|
||||||
impl Contract {
|
|
||||||
#doc
|
|
||||||
pub fn builder<F, T>(
|
|
||||||
web3: &self::ethcontract::web3::api::Web3<T> #lib_input #input ,
|
|
||||||
) -> self::ethcontract::dyns::DynDeployBuilder<Self>
|
|
||||||
where
|
|
||||||
F: self::ethcontract::web3::futures::Future<
|
|
||||||
Item = self::ethcontract::json::Value,
|
|
||||||
Error = self::ethcontract::web3::Error,
|
|
||||||
> + Send + 'static,
|
|
||||||
T: self::ethcontract::web3::Transport<Out = F> + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
use self::ethcontract::dyns::DynTransport;
|
|
||||||
use self::ethcontract::contract::DeployBuilder;
|
|
||||||
use self::ethcontract::web3::api::Web3;
|
|
||||||
|
|
||||||
let transport = DynTransport::new(web3.transport().clone());
|
|
||||||
let web3 = Web3::new(transport);
|
|
||||||
|
|
||||||
let bytecode = Self::artifact().bytecode.clone();
|
|
||||||
#link
|
|
||||||
|
|
||||||
DeployBuilder::new(web3, bytecode, #arg).expect("valid deployment args")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl self::ethcontract::contract::Deploy<self::ethcontract::dyns::DynTransport> for Contract {
|
|
||||||
type Context = self::ethcontract::common::Bytecode;
|
|
||||||
|
|
||||||
fn bytecode(cx: &Self::Context) -> &self::ethcontract::common::Bytecode {
|
|
||||||
cx
|
|
||||||
}
|
|
||||||
|
|
||||||
fn abi(_: &Self::Context) -> &self::ethcontract::common::Abi {
|
|
||||||
&Self::artifact().abi
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_deployment(
|
|
||||||
web3: self::ethcontract::dyns::DynWeb3,
|
|
||||||
address: self::ethcontract::Address,
|
|
||||||
transaction_hash: self::ethcontract::H256,
|
|
||||||
_: Self::Context,
|
|
||||||
) -> Self {
|
|
||||||
Self::with_transaction(&web3, address, Some(transaction_hash))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,855 +0,0 @@
|
||||||
use crate::contract::{types, Context};
|
|
||||||
use crate::util;
|
|
||||||
use anyhow::Result;
|
|
||||||
use ethcontract_common::abi::{Event, EventParam, Hash, ParamType};
|
|
||||||
use ethcontract_common::abiext::EventExt;
|
|
||||||
use inflector::Inflector;
|
|
||||||
use proc_macro2::{Literal, TokenStream};
|
|
||||||
use quote::quote;
|
|
||||||
use syn::Path;
|
|
||||||
|
|
||||||
pub(crate) fn expand(cx: &Context) -> Result<TokenStream> {
|
|
||||||
let structs_mod = expand_structs_mod(cx)?;
|
|
||||||
let filters = expand_filters(cx)?;
|
|
||||||
let all_events = expand_all_events(cx);
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
#structs_mod
|
|
||||||
#filters
|
|
||||||
#all_events
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands into a module containing all the event data structures from the ABI.
|
|
||||||
fn expand_structs_mod(cx: &Context) -> Result<TokenStream> {
|
|
||||||
let data_types = cx
|
|
||||||
.artifact
|
|
||||||
.abi
|
|
||||||
.events()
|
|
||||||
.map(|event| expand_data_type(event, &cx.event_derives))
|
|
||||||
.collect::<Result<Vec<_>>>()?;
|
|
||||||
if data_types.is_empty() {
|
|
||||||
return Ok(quote! {});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
/// Module containing all generated data models for this contract's
|
|
||||||
/// events.
|
|
||||||
pub mod event_data {
|
|
||||||
use super::ethcontract;
|
|
||||||
|
|
||||||
#( #data_types )*
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand_derives(derives: &[Path]) -> TokenStream {
|
|
||||||
quote! {#(#derives),*}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands an ABI event into a single event data type. This can expand either
|
|
||||||
/// into a structure or a tuple in the case where all event parameters (topics
|
|
||||||
/// and data) are anonymous.
|
|
||||||
fn expand_data_type(event: &Event, event_derives: &[Path]) -> Result<TokenStream> {
|
|
||||||
let event_name = expand_struct_name(event);
|
|
||||||
|
|
||||||
let signature = expand_hash(event.signature());
|
|
||||||
|
|
||||||
let abi_signature = event.abi_signature();
|
|
||||||
let abi_signature_lit = Literal::string(&abi_signature);
|
|
||||||
let abi_signature_doc = util::expand_doc(&format!("`{}`", abi_signature));
|
|
||||||
|
|
||||||
let params = expand_params(event)?;
|
|
||||||
|
|
||||||
let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
|
|
||||||
let (data_type_definition, data_type_construction) = if all_anonymous_fields {
|
|
||||||
expand_data_tuple(&event_name, ¶ms)
|
|
||||||
} else {
|
|
||||||
expand_data_struct(&event_name, ¶ms)
|
|
||||||
};
|
|
||||||
|
|
||||||
let params_len = Literal::usize_unsuffixed(params.len());
|
|
||||||
let read_param_token = params
|
|
||||||
.iter()
|
|
||||||
.map(|(name, ty)| {
|
|
||||||
quote! {
|
|
||||||
let #name = <#ty as self::ethcontract::web3::contract::tokens::Tokenizable>
|
|
||||||
::from_token(tokens.next().unwrap())?;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let derives = expand_derives(event_derives);
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, #derives)]
|
|
||||||
pub #data_type_definition
|
|
||||||
|
|
||||||
impl #event_name {
|
|
||||||
/// Retrieves the signature for the event this data corresponds to.
|
|
||||||
/// This signature is the Keccak-256 hash of the ABI signature of
|
|
||||||
/// this event.
|
|
||||||
pub fn signature() -> self::ethcontract::H256 {
|
|
||||||
#signature
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves the ABI signature for the event this data corresponds
|
|
||||||
/// to. For this event the value should always be:
|
|
||||||
///
|
|
||||||
#abi_signature_doc
|
|
||||||
pub fn abi_signature() -> &'static str {
|
|
||||||
#abi_signature_lit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl self::ethcontract::web3::contract::tokens::Detokenize for #event_name {
|
|
||||||
fn from_tokens(
|
|
||||||
tokens: Vec<self::ethcontract::common::abi::Token>,
|
|
||||||
) -> Result<Self, self::ethcontract::web3::contract::Error> {
|
|
||||||
if tokens.len() != #params_len {
|
|
||||||
return Err(self::ethcontract::web3::contract::Error::InvalidOutputType(format!(
|
|
||||||
"Expected {} tokens, got {}: {:?}",
|
|
||||||
#params_len,
|
|
||||||
tokens.len(),
|
|
||||||
tokens
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
|
||||||
let mut tokens = tokens.into_iter();
|
|
||||||
#( #read_param_token )*
|
|
||||||
|
|
||||||
Ok(#data_type_construction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands an ABI event into an identifier for its event data type.
|
|
||||||
fn expand_struct_name(event: &Event) -> TokenStream {
|
|
||||||
let event_name = util::ident(&event.name.to_pascal_case());
|
|
||||||
quote! { #event_name }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands an ABI event into name-type pairs for each of its parameters.
|
|
||||||
fn expand_params(event: &Event) -> Result<Vec<(TokenStream, TokenStream)>> {
|
|
||||||
event
|
|
||||||
.inputs
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, input)| {
|
|
||||||
// NOTE: Events can contain nameless values.
|
|
||||||
let name = util::expand_input_name(i, &input.name);
|
|
||||||
let ty = expand_input_type(&input)?;
|
|
||||||
|
|
||||||
Ok((name, ty))
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands an event data structure from its name-type parameter pairs. Returns
|
|
||||||
/// a tuple with the type definition (i.e. the struct declaration) and
|
|
||||||
/// construction (i.e. code for creating an instance of the event data).
|
|
||||||
fn expand_data_struct(
|
|
||||||
name: &TokenStream,
|
|
||||||
params: &[(TokenStream, TokenStream)],
|
|
||||||
) -> (TokenStream, TokenStream) {
|
|
||||||
let fields = params
|
|
||||||
.iter()
|
|
||||||
.map(|(name, ty)| quote! { pub #name: #ty })
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let param_names = params
|
|
||||||
.iter()
|
|
||||||
.map(|(name, _)| name)
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let definition = quote! { struct #name { #( #fields, )* } };
|
|
||||||
let construction = quote! { #name { #( #param_names ),* } };
|
|
||||||
|
|
||||||
(definition, construction)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands an event data named tuple from its name-type parameter pairs.
|
|
||||||
/// Returns a tuple with the type definition and construction.
|
|
||||||
fn expand_data_tuple(
|
|
||||||
name: &TokenStream,
|
|
||||||
params: &[(TokenStream, TokenStream)],
|
|
||||||
) -> (TokenStream, TokenStream) {
|
|
||||||
let fields = params
|
|
||||||
.iter()
|
|
||||||
.map(|(_, ty)| quote! { pub #ty })
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let param_names = params
|
|
||||||
.iter()
|
|
||||||
.map(|(name, _)| name)
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let definition = quote! { struct #name( #( #fields ),* ); };
|
|
||||||
let construction = quote! { #name( #( #param_names ),* ) };
|
|
||||||
|
|
||||||
(definition, construction)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands into an `Events` type with method definitions for creating event
|
|
||||||
/// streams for all non-anonymous contract events in the ABI.
|
|
||||||
fn expand_filters(cx: &Context) -> Result<TokenStream> {
|
|
||||||
let standard_events = cx
|
|
||||||
.artifact
|
|
||||||
.abi
|
|
||||||
.events()
|
|
||||||
.filter(|event| !event.anonymous)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if standard_events.is_empty() {
|
|
||||||
return Ok(quote! {});
|
|
||||||
}
|
|
||||||
|
|
||||||
let filters = standard_events
|
|
||||||
.iter()
|
|
||||||
.map(|event| expand_filter(event))
|
|
||||||
.collect::<Result<Vec<_>>>()?;
|
|
||||||
let builders = standard_events
|
|
||||||
.iter()
|
|
||||||
.map(|event| expand_builder_type(event))
|
|
||||||
.collect::<Result<Vec<_>>>()?;
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
impl Contract {
|
|
||||||
/// Retrieves a handle to a type containing for creating event
|
|
||||||
/// streams for all the contract events.
|
|
||||||
pub fn events(&self) -> Events<'_> {
|
|
||||||
Events {
|
|
||||||
instance: self.raw_instance(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Events<'a> {
|
|
||||||
instance: &'a self::ethcontract::dyns::DynInstance,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Events<'_> {
|
|
||||||
#( #filters )*
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Module containing the generated event stream builders with type safe
|
|
||||||
/// filter methods for this contract's events.
|
|
||||||
pub mod event_builders {
|
|
||||||
use super::ethcontract;
|
|
||||||
use super::event_data;
|
|
||||||
|
|
||||||
#( #builders )*
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands into a single method for contracting an event stream.
|
|
||||||
fn expand_filter(event: &Event) -> Result<TokenStream> {
|
|
||||||
let name = util::safe_ident(&event.name.to_snake_case());
|
|
||||||
let builder_name = expand_builder_name(event);
|
|
||||||
let signature = expand_hash(event.signature());
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
/// Generated by `ethcontract`.
|
|
||||||
pub fn #name(&self) -> self::event_builders::#builder_name {
|
|
||||||
self::event_builders::#builder_name(
|
|
||||||
self.instance.event(#signature)
|
|
||||||
.expect("generated event filter"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands an ABI event into a wrapped `EventBuilder` type with type-safe
|
|
||||||
/// filter methods.
|
|
||||||
fn expand_builder_type(event: &Event) -> Result<TokenStream> {
|
|
||||||
let event_name = expand_struct_name(event);
|
|
||||||
let builder_doc = util::expand_doc(&format!(
|
|
||||||
"A builder for creating a filtered stream of `{}` events.",
|
|
||||||
event_name
|
|
||||||
));
|
|
||||||
let builder_name = expand_builder_name(event);
|
|
||||||
let topic_filters = expand_builder_topic_filters(event)?;
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
#builder_doc
|
|
||||||
pub struct #builder_name(
|
|
||||||
/// The inner event builder.
|
|
||||||
pub self::ethcontract::dyns::DynEventBuilder<self::event_data::#event_name>,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl #builder_name {
|
|
||||||
/// Sets the starting block from which to stream logs for.
|
|
||||||
///
|
|
||||||
/// If left unset defaults to the latest block.
|
|
||||||
#[allow(clippy::wrong_self_convention)]
|
|
||||||
pub fn from_block(mut self, block: self::ethcontract::BlockNumber) -> Self {
|
|
||||||
self.0 = (self.0).from_block(block);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the last block from which to stream logs for.
|
|
||||||
///
|
|
||||||
/// If left unset defaults to the streaming until the end of days.
|
|
||||||
#[allow(clippy::wrong_self_convention)]
|
|
||||||
pub fn to_block(mut self, block: self::ethcontract::BlockNumber) -> Self {
|
|
||||||
self.0 = (self.0).to_block(block);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Limit the number of events that can be retrieved by this filter.
|
|
||||||
///
|
|
||||||
/// Note that this parameter is non-standard.
|
|
||||||
pub fn limit(mut self, value: usize) -> Self {
|
|
||||||
self.0 = (self.0).limit(value);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The polling interval. This is used as the interval between
|
|
||||||
/// consecutive `eth_getFilterChanges` calls to get filter updates.
|
|
||||||
pub fn poll_interval(mut self, value: std::time::Duration) -> Self {
|
|
||||||
self.0 = (self.0).poll_interval(value);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#topic_filters
|
|
||||||
|
|
||||||
/// Returns a future that resolves with a collection of all existing
|
|
||||||
/// logs matching the builder parameters.
|
|
||||||
pub async fn query(self) -> std::result::Result<
|
|
||||||
std::vec::Vec<self::ethcontract::Event<self::event_data::#event_name>>,
|
|
||||||
self::ethcontract::errors::EventError,
|
|
||||||
> {
|
|
||||||
(self.0).query().await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an event stream from the current event builder.
|
|
||||||
pub fn stream(self) -> impl self::ethcontract::futures::stream::Stream<
|
|
||||||
Item = std::result::Result<
|
|
||||||
self::ethcontract::StreamEvent<self::event_data::#event_name>,
|
|
||||||
self::ethcontract::errors::EventError,
|
|
||||||
>,
|
|
||||||
> {
|
|
||||||
(self.0).stream()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands an ABI event into filter methods for its indexed parameters.
|
|
||||||
fn expand_builder_topic_filters(event: &Event) -> Result<TokenStream> {
|
|
||||||
let topic_filters = event
|
|
||||||
.inputs
|
|
||||||
.iter()
|
|
||||||
.filter(|input| input.indexed)
|
|
||||||
.enumerate()
|
|
||||||
.map(|(topic_index, input)| expand_builder_topic_filter(topic_index, input))
|
|
||||||
.collect::<Result<Vec<_>>>()?;
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
#( #topic_filters )*
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands a event parameter into an event builder filter method for the
|
|
||||||
/// specified topic index.
|
|
||||||
fn expand_builder_topic_filter(topic_index: usize, param: &EventParam) -> Result<TokenStream> {
|
|
||||||
let doc = util::expand_doc(&format!(
|
|
||||||
"Adds a filter for the {} event parameter.",
|
|
||||||
param.name,
|
|
||||||
));
|
|
||||||
let topic = util::ident(&format!("topic{}", topic_index));
|
|
||||||
let name = if param.name.is_empty() {
|
|
||||||
topic.clone()
|
|
||||||
} else {
|
|
||||||
util::safe_ident(¶m.name.to_snake_case())
|
|
||||||
};
|
|
||||||
let ty = expand_input_type(¶m)?;
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
#doc
|
|
||||||
pub fn #name(mut self, topic: self::ethcontract::Topic<#ty>) -> Self {
|
|
||||||
self.0 = (self.0).#topic(topic);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands an ABI event into an identifier for its event data type.
|
|
||||||
fn expand_builder_name(event: &Event) -> TokenStream {
|
|
||||||
let builder_name = util::ident(&format!("{}Builder", &event.name.to_pascal_case()));
|
|
||||||
quote! { #builder_name }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands into the `all_events` method on the root contract type if it
|
|
||||||
/// contains events. Expands to nothing otherwise.
|
|
||||||
fn expand_all_events(cx: &Context) -> TokenStream {
|
|
||||||
if cx.artifact.abi.events.is_empty() {
|
|
||||||
return quote! {};
|
|
||||||
}
|
|
||||||
|
|
||||||
let event_enum = expand_event_enum(cx);
|
|
||||||
let event_parse_log = expand_event_parse_log(cx);
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
impl Contract {
|
|
||||||
/// Returns a log stream with all events.
|
|
||||||
pub fn all_events(&self) -> self::ethcontract::dyns::DynAllEventsBuilder<Event> {
|
|
||||||
self::ethcontract::dyns::DynAllEventsBuilder::new(
|
|
||||||
self.raw_instance().web3(),
|
|
||||||
self.address(),
|
|
||||||
self.transaction_hash(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#event_enum
|
|
||||||
#event_parse_log
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands into an enum with one variant for each distinct event type,
|
|
||||||
/// including anonymous types.
|
|
||||||
fn expand_event_enum(cx: &Context) -> TokenStream {
|
|
||||||
let variants = {
|
|
||||||
let mut events = cx.artifact.abi.events().collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// NOTE: We sort the events by name so that the generated enum is
|
|
||||||
// consistent. This also faciliates testing as so that the same ABI
|
|
||||||
// yields consistent code.
|
|
||||||
events.sort_unstable_by_key(|event| &event.name);
|
|
||||||
|
|
||||||
events
|
|
||||||
.into_iter()
|
|
||||||
.map(|event| {
|
|
||||||
let struct_name = expand_struct_name(&event);
|
|
||||||
quote! {
|
|
||||||
#struct_name(self::event_data::#struct_name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
};
|
|
||||||
|
|
||||||
let derives = expand_derives(&cx.event_derives);
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
/// A contract event.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, #derives)]
|
|
||||||
pub enum Event {
|
|
||||||
#( #variants, )*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands the `ParseLog` implementation for the event enum.
|
|
||||||
fn expand_event_parse_log(cx: &Context) -> TokenStream {
|
|
||||||
let all_events = {
|
|
||||||
let mut all_events = cx
|
|
||||||
.artifact
|
|
||||||
.abi
|
|
||||||
.events()
|
|
||||||
.map(|event| {
|
|
||||||
let struct_name = expand_struct_name(&event);
|
|
||||||
|
|
||||||
let name = Literal::string(&event.name);
|
|
||||||
let decode_event = quote! {
|
|
||||||
log.clone().decode(
|
|
||||||
&Contract::artifact()
|
|
||||||
.abi
|
|
||||||
.event(#name)
|
|
||||||
.expect("generated event decode")
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
(event, struct_name, decode_event)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// NOTE: We sort the events by name so that the anonymous error decoding
|
|
||||||
// is consistent. Since the events are stored in a `HashMap`, there is
|
|
||||||
// no guaranteed order, and in the case where there is ambiguity in
|
|
||||||
// decoding anonymous events, its nice if they follow some strict and
|
|
||||||
// predictable order.
|
|
||||||
all_events.sort_unstable_by_key(|(event, _, _)| &event.name);
|
|
||||||
all_events
|
|
||||||
};
|
|
||||||
|
|
||||||
let standard_event_match_arms = all_events
|
|
||||||
.iter()
|
|
||||||
.filter(|(event, _, _)| !event.anonymous)
|
|
||||||
.map(|(event, struct_name, decode_event)| {
|
|
||||||
// These are all possible stardard (i.e. non-anonymous) events that
|
|
||||||
// the contract can produce, along with its signature and index in
|
|
||||||
// the contract ABI. For these, we match topic 0 to the signature
|
|
||||||
// and try to decode.
|
|
||||||
|
|
||||||
let signature = expand_hash(event.signature());
|
|
||||||
quote! {
|
|
||||||
#signature => Ok(Event::#struct_name(#decode_event?)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let anonymous_event_try_decode = all_events
|
|
||||||
.iter()
|
|
||||||
.filter(|(event, _, _)| event.anonymous)
|
|
||||||
.map(|(_, struct_name, decode_event)| {
|
|
||||||
// For anonymous events, just try to decode one at a time and return
|
|
||||||
// the first that succeeds.
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
if let Ok(data) = #decode_event {
|
|
||||||
return Ok(Event::#struct_name(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let invalid_data = expand_invalid_data();
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
impl self::ethcontract::contract::ParseLog for Event {
|
|
||||||
fn parse_log(
|
|
||||||
log: self::ethcontract::RawLog,
|
|
||||||
) -> Result<Self, self::ethcontract::errors::ExecutionError> {
|
|
||||||
let standard_event = log.topics
|
|
||||||
.get(0)
|
|
||||||
.copied()
|
|
||||||
.map(|topic| match topic {
|
|
||||||
#( #standard_event_match_arms )*
|
|
||||||
_ => #invalid_data,
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(Ok(data)) = standard_event {
|
|
||||||
return Ok(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#( #anonymous_event_try_decode )*
|
|
||||||
|
|
||||||
#invalid_data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands an event property type.
|
|
||||||
///
|
|
||||||
/// Note that this is slightly different than an expanding a Solidity type as
|
|
||||||
/// complex types like arrays and strings get emited as hashes when they are
|
|
||||||
/// indexed.
|
|
||||||
fn expand_input_type(input: &EventParam) -> Result<TokenStream> {
|
|
||||||
Ok(match (&input.kind, input.indexed) {
|
|
||||||
(ParamType::Array(..), true)
|
|
||||||
| (ParamType::Bytes, true)
|
|
||||||
| (ParamType::FixedArray(..), true)
|
|
||||||
| (ParamType::String, true)
|
|
||||||
| (ParamType::Tuple(..), true) => {
|
|
||||||
quote! { self::ethcontract::H256 }
|
|
||||||
}
|
|
||||||
(kind, _) => types::expand(kind)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands a 256-bit `Hash` into a literal representation that can be used with
|
|
||||||
/// quasi-quoting for code generation.
|
|
||||||
fn expand_hash(hash: Hash) -> TokenStream {
|
|
||||||
let bytes = hash.as_bytes().iter().copied().map(Literal::u8_unsuffixed);
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
self::ethcontract::H256([#( #bytes ),*])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands to a generic `InvalidData` error.
|
|
||||||
fn expand_invalid_data() -> TokenStream {
|
|
||||||
quote! {
|
|
||||||
Err(self::ethcontract::errors::ExecutionError::from(
|
|
||||||
self::ethcontract::common::abi::Error::InvalidData
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use ethcontract_common::abi::{EventParam, ParamType};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expand_empty_filters() {
|
|
||||||
assert_quote!(expand_filters(&Context::default()).unwrap(), {});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expand_transfer_filter() {
|
|
||||||
let event = Event {
|
|
||||||
name: "Transfer".into(),
|
|
||||||
inputs: vec![
|
|
||||||
EventParam {
|
|
||||||
name: "from".into(),
|
|
||||||
kind: ParamType::Address,
|
|
||||||
indexed: true,
|
|
||||||
},
|
|
||||||
EventParam {
|
|
||||||
name: "to".into(),
|
|
||||||
kind: ParamType::Address,
|
|
||||||
indexed: true,
|
|
||||||
},
|
|
||||||
EventParam {
|
|
||||||
name: "amount".into(),
|
|
||||||
kind: ParamType::Uint(256),
|
|
||||||
indexed: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
anonymous: false,
|
|
||||||
};
|
|
||||||
let signature = expand_hash(event.signature());
|
|
||||||
|
|
||||||
assert_quote!(expand_filter(&event).unwrap(), {
|
|
||||||
/// Generated by `ethcontract`.
|
|
||||||
pub fn transfer(&self) -> self::event_builders::TransferBuilder {
|
|
||||||
self::event_builders::TransferBuilder(
|
|
||||||
self.instance.event(#signature)
|
|
||||||
.expect("generated event filter"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expand_transfer_builder_topic_filters() {
|
|
||||||
let event = Event {
|
|
||||||
name: "Transfer".into(),
|
|
||||||
inputs: vec![
|
|
||||||
EventParam {
|
|
||||||
name: "from".into(),
|
|
||||||
kind: ParamType::Address,
|
|
||||||
indexed: true,
|
|
||||||
},
|
|
||||||
EventParam {
|
|
||||||
name: "to".into(),
|
|
||||||
kind: ParamType::Address,
|
|
||||||
indexed: true,
|
|
||||||
},
|
|
||||||
EventParam {
|
|
||||||
name: "amount".into(),
|
|
||||||
kind: ParamType::Uint(256),
|
|
||||||
indexed: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
anonymous: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
assert_quote!(expand_builder_topic_filters(&event).unwrap(), {
|
|
||||||
#[doc = "Adds a filter for the from event parameter."]
|
|
||||||
pub fn from(mut self, topic: self::ethcontract::Topic<self::ethcontract::Address>) -> Self {
|
|
||||||
self.0 = (self.0).topic0(topic);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc = "Adds a filter for the to event parameter."]
|
|
||||||
pub fn to(mut self, topic: self::ethcontract::Topic<self::ethcontract::Address>) -> Self {
|
|
||||||
self.0 = (self.0).topic1(topic);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expand_data_struct_value() {
|
|
||||||
let event = Event {
|
|
||||||
name: "Foo".into(),
|
|
||||||
inputs: vec![
|
|
||||||
EventParam {
|
|
||||||
name: "a".into(),
|
|
||||||
kind: ParamType::Bool,
|
|
||||||
indexed: false,
|
|
||||||
},
|
|
||||||
EventParam {
|
|
||||||
name: String::new(),
|
|
||||||
kind: ParamType::Address,
|
|
||||||
indexed: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
anonymous: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let name = expand_struct_name(&event);
|
|
||||||
let params = expand_params(&event).unwrap();
|
|
||||||
let (definition, construction) = expand_data_struct(&name, ¶ms);
|
|
||||||
|
|
||||||
assert_quote!(definition, {
|
|
||||||
struct Foo {
|
|
||||||
pub a: bool,
|
|
||||||
pub p1: self::ethcontract::Address,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
assert_quote!(construction, { Foo { a, p1 } });
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expand_data_tuple_value() {
|
|
||||||
let event = Event {
|
|
||||||
name: "Foo".into(),
|
|
||||||
inputs: vec![
|
|
||||||
EventParam {
|
|
||||||
name: String::new(),
|
|
||||||
kind: ParamType::Bool,
|
|
||||||
indexed: false,
|
|
||||||
},
|
|
||||||
EventParam {
|
|
||||||
name: String::new(),
|
|
||||||
kind: ParamType::Address,
|
|
||||||
indexed: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
anonymous: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let name = expand_struct_name(&event);
|
|
||||||
let params = expand_params(&event).unwrap();
|
|
||||||
let (definition, construction) = expand_data_tuple(&name, ¶ms);
|
|
||||||
|
|
||||||
assert_quote!(definition, {
|
|
||||||
struct Foo(pub bool, pub self::ethcontract::Address);
|
|
||||||
});
|
|
||||||
assert_quote!(construction, { Foo(p0, p1) });
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expand_enum_for_all_events() {
|
|
||||||
let context = {
|
|
||||||
let mut context = Context::default();
|
|
||||||
context.artifact.abi.events.insert(
|
|
||||||
"Foo".into(),
|
|
||||||
vec![Event {
|
|
||||||
name: "Foo".into(),
|
|
||||||
inputs: vec![EventParam {
|
|
||||||
name: String::new(),
|
|
||||||
kind: ParamType::Bool,
|
|
||||||
indexed: false,
|
|
||||||
}],
|
|
||||||
anonymous: false,
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
context.artifact.abi.events.insert(
|
|
||||||
"Bar".into(),
|
|
||||||
vec![Event {
|
|
||||||
name: "Bar".into(),
|
|
||||||
inputs: vec![EventParam {
|
|
||||||
name: String::new(),
|
|
||||||
kind: ParamType::Address,
|
|
||||||
indexed: false,
|
|
||||||
}],
|
|
||||||
anonymous: true,
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
context.event_derives = ["Asdf", "a::B", "a::b::c::D"]
|
|
||||||
.iter()
|
|
||||||
.map(|derive| syn::parse_str::<Path>(derive).unwrap())
|
|
||||||
.collect();
|
|
||||||
context
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_quote!(expand_event_enum(&context), {
|
|
||||||
/// A contract event.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Asdf, a::B, a::b::c::D)]
|
|
||||||
pub enum Event {
|
|
||||||
Bar(self::event_data::Bar),
|
|
||||||
Foo(self::event_data::Foo),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expand_parse_log_impl_for_all_events() {
|
|
||||||
let context = {
|
|
||||||
let mut context = Context::default();
|
|
||||||
context.artifact.abi.events.insert(
|
|
||||||
"Foo".into(),
|
|
||||||
vec![Event {
|
|
||||||
name: "Foo".into(),
|
|
||||||
inputs: vec![EventParam {
|
|
||||||
name: String::new(),
|
|
||||||
kind: ParamType::Bool,
|
|
||||||
indexed: false,
|
|
||||||
}],
|
|
||||||
anonymous: false,
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
context.artifact.abi.events.insert(
|
|
||||||
"Bar".into(),
|
|
||||||
vec![Event {
|
|
||||||
name: "Bar".into(),
|
|
||||||
inputs: vec![EventParam {
|
|
||||||
name: String::new(),
|
|
||||||
kind: ParamType::Address,
|
|
||||||
indexed: false,
|
|
||||||
}],
|
|
||||||
anonymous: true,
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
context
|
|
||||||
};
|
|
||||||
|
|
||||||
let foo_signature = expand_hash(context.artifact.abi.event("Foo").unwrap().signature());
|
|
||||||
let invalid_data = expand_invalid_data();
|
|
||||||
|
|
||||||
assert_quote!(expand_event_parse_log(&context), {
|
|
||||||
impl self::ethcontract::contract::ParseLog for Event {
|
|
||||||
fn parse_log(
|
|
||||||
log: self::ethcontract::RawLog,
|
|
||||||
) -> Result<Self, self::ethcontract::errors::ExecutionError> {
|
|
||||||
let standard_event = log.topics
|
|
||||||
.get(0)
|
|
||||||
.copied()
|
|
||||||
.map(|topic| match topic {
|
|
||||||
#foo_signature => Ok(Event::Foo(
|
|
||||||
log.clone().decode(
|
|
||||||
&Contract::artifact()
|
|
||||||
.abi
|
|
||||||
.event("Foo")
|
|
||||||
.expect("generated event decode")
|
|
||||||
)?
|
|
||||||
)),
|
|
||||||
_ => #invalid_data,
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(Ok(data)) = standard_event {
|
|
||||||
return Ok(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(data) = log.clone().decode(
|
|
||||||
&Contract::artifact()
|
|
||||||
.abi
|
|
||||||
.event("Bar")
|
|
||||||
.expect("generated event decode")
|
|
||||||
) {
|
|
||||||
return Ok(Event::Bar(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
#invalid_data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[rustfmt::skip]
|
|
||||||
fn expand_hash_value() {
|
|
||||||
assert_quote!(
|
|
||||||
expand_hash(
|
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f".parse().unwrap()
|
|
||||||
),
|
|
||||||
{
|
|
||||||
self::ethcontract::H256([
|
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
|
||||||
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
|
|
||||||
])
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,255 +0,0 @@
|
||||||
use crate::contract::{types, Context};
|
|
||||||
use crate::util;
|
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
|
||||||
use ethcontract_common::abi::{Function, Param};
|
|
||||||
use ethcontract_common::abiext::FunctionExt;
|
|
||||||
use ethcontract_common::hash::H32;
|
|
||||||
use inflector::Inflector;
|
|
||||||
use proc_macro2::{Literal, TokenStream};
|
|
||||||
use quote::quote;
|
|
||||||
use syn::Ident;
|
|
||||||
|
|
||||||
pub(crate) fn expand(cx: &Context) -> Result<TokenStream> {
|
|
||||||
let functions = expand_functions(cx)?;
|
|
||||||
let fallback = expand_fallback(cx);
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
#functions
|
|
||||||
#fallback
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands a context into a method struct containing all the generated bindings
|
|
||||||
/// to the Solidity contract methods.
|
|
||||||
fn expand_functions(cx: &Context) -> Result<TokenStream> {
|
|
||||||
let mut aliases = cx.method_aliases.clone();
|
|
||||||
let functions = cx
|
|
||||||
.artifact
|
|
||||||
.abi
|
|
||||||
.functions()
|
|
||||||
.map(|function| {
|
|
||||||
let signature = function.abi_signature();
|
|
||||||
expand_function(&cx, function, aliases.remove(&signature))
|
|
||||||
.with_context(|| format!("error expanding function '{}'", signature))
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>>>()?;
|
|
||||||
if let Some(unused) = aliases.keys().next() {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"a manual method alias for '{}' was specified but this method does not exist",
|
|
||||||
unused,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let methods_attrs = quote! { #[derive(Clone)] };
|
|
||||||
let methods_struct = quote! {
|
|
||||||
struct Methods {
|
|
||||||
instance: self::ethcontract::dyns::DynInstance,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if functions.is_empty() {
|
|
||||||
// NOTE: The methods struct is still needed when there are no functions
|
|
||||||
// as it contains the the runtime instance. The code is setup this way
|
|
||||||
// so that the contract can implement `Deref` targetting the methods
|
|
||||||
// struct and, therefore, call the methods directly.
|
|
||||||
return Ok(quote! {
|
|
||||||
#methods_attrs
|
|
||||||
#methods_struct
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
impl Contract {
|
|
||||||
/// Retrives a reference to type containing all the generated
|
|
||||||
/// contract methods. This can be used for methods where the name
|
|
||||||
/// would collide with a common method (like `at` or `deployed`).
|
|
||||||
pub fn methods(&self) -> &Methods {
|
|
||||||
&self.methods
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Type containing all contract methods for generated contract type.
|
|
||||||
#methods_attrs
|
|
||||||
pub #methods_struct
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
|
||||||
impl Methods {
|
|
||||||
#( #functions )*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Deref for Contract {
|
|
||||||
type Target = Methods;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.methods
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand_function(cx: &Context, function: &Function, alias: Option<Ident>) -> Result<TokenStream> {
|
|
||||||
let name = alias.unwrap_or_else(|| util::safe_ident(&function.name.to_snake_case()));
|
|
||||||
let signature = function.abi_signature();
|
|
||||||
let selector = expand_selector(function.selector());
|
|
||||||
|
|
||||||
let doc_str = cx
|
|
||||||
.artifact
|
|
||||||
.devdoc
|
|
||||||
.methods
|
|
||||||
.get(&signature)
|
|
||||||
.or_else(|| cx.artifact.userdoc.methods.get(&signature))
|
|
||||||
.and_then(|entry| entry.details.as_ref())
|
|
||||||
.map(String::as_str)
|
|
||||||
.unwrap_or("Generated by `ethcontract`");
|
|
||||||
let doc = util::expand_doc(doc_str);
|
|
||||||
|
|
||||||
let input = expand_inputs(&function.inputs)?;
|
|
||||||
let outputs = expand_fn_outputs(&function.outputs)?;
|
|
||||||
let (method, result_type_name) = if function.constant {
|
|
||||||
(quote! { view_method }, quote! { DynViewMethodBuilder })
|
|
||||||
} else {
|
|
||||||
(quote! { method }, quote! { DynMethodBuilder })
|
|
||||||
};
|
|
||||||
let result = quote! { self::ethcontract::dyns::#result_type_name<#outputs> };
|
|
||||||
let arg = expand_inputs_call_arg(&function.inputs);
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
#doc
|
|
||||||
pub fn #name(&self #input) -> #result {
|
|
||||||
self.instance.#method(#selector, #arg)
|
|
||||||
.expect("generated call")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn expand_inputs(inputs: &[Param]) -> Result<TokenStream> {
|
|
||||||
let params = inputs
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, param)| {
|
|
||||||
let name = util::expand_input_name(i, ¶m.name);
|
|
||||||
let kind = types::expand(¶m.kind)?;
|
|
||||||
Ok(quote! { #name: #kind })
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>>>()?;
|
|
||||||
Ok(quote! { #( , #params )* })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn expand_inputs_call_arg(inputs: &[Param]) -> TokenStream {
|
|
||||||
let names = inputs
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, param)| util::expand_input_name(i, ¶m.name));
|
|
||||||
quote! { ( #( #names ,)* ) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand_fn_outputs(outputs: &[Param]) -> Result<TokenStream> {
|
|
||||||
match outputs.len() {
|
|
||||||
0 => Ok(quote! { self::ethcontract::Void }),
|
|
||||||
1 => types::expand(&outputs[0].kind),
|
|
||||||
_ => {
|
|
||||||
let types = outputs
|
|
||||||
.iter()
|
|
||||||
.map(|param| types::expand(¶m.kind))
|
|
||||||
.collect::<Result<Vec<_>>>()?;
|
|
||||||
Ok(quote! { (#( #types ),*) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand_selector(selector: H32) -> TokenStream {
|
|
||||||
let bytes = selector.iter().copied().map(Literal::u8_unsuffixed);
|
|
||||||
quote! { [#( #bytes ),*] }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expands a context into fallback method when the contract implements one,
|
|
||||||
/// and an empty token stream otherwise.
|
|
||||||
fn expand_fallback(cx: &Context) -> TokenStream {
|
|
||||||
if cx.artifact.abi.fallback {
|
|
||||||
quote! {
|
|
||||||
impl Contract {
|
|
||||||
/// Returns a method builder to setup a call to a smart
|
|
||||||
/// contract's fallback function.
|
|
||||||
pub fn fallback<D>(&self, data: D) -> self::ethcontract::dyns::DynMethodBuilder<
|
|
||||||
self::ethcontract::Void,
|
|
||||||
>
|
|
||||||
where
|
|
||||||
D: Into<Vec<u8>>,
|
|
||||||
{
|
|
||||||
self.raw_instance().fallback(data)
|
|
||||||
.expect("generated fallback method")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use ethcontract_common::abi::ParamType;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expand_inputs_empty() {
|
|
||||||
assert_quote!(expand_inputs(&[]).unwrap().to_string(), {},);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expand_inputs_() {
|
|
||||||
assert_quote!(
|
|
||||||
expand_inputs(
|
|
||||||
|
|
||||||
&[
|
|
||||||
Param {
|
|
||||||
name: "a".to_string(),
|
|
||||||
kind: ParamType::Bool,
|
|
||||||
},
|
|
||||||
Param {
|
|
||||||
name: "b".to_string(),
|
|
||||||
kind: ParamType::Address,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
{ , a: bool, b: self::ethcontract::Address },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expand_fn_outputs_empty() {
|
|
||||||
assert_quote!(expand_fn_outputs(&[],).unwrap(), {
|
|
||||||
self::ethcontract::Void
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expand_fn_outputs_single() {
|
|
||||||
assert_quote!(
|
|
||||||
expand_fn_outputs(&[Param {
|
|
||||||
name: "a".to_string(),
|
|
||||||
kind: ParamType::Bool,
|
|
||||||
}])
|
|
||||||
.unwrap(),
|
|
||||||
{ bool },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn expand_fn_outputs_muliple() {
|
|
||||||
assert_quote!(
|
|
||||||
expand_fn_outputs(&[
|
|
||||||
Param {
|
|
||||||
name: "a".to_string(),
|
|
||||||
kind: ParamType::Bool,
|
|
||||||
},
|
|
||||||
Param {
|
|
||||||
name: "b".to_string(),
|
|
||||||
kind: ParamType::Address,
|
|
||||||
},
|
|
||||||
],)
|
|
||||||
.unwrap(),
|
|
||||||
{ (bool, self::ethcontract::Address) },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -89,7 +89,9 @@ pub struct Filter {
|
||||||
|
|
||||||
impl Filter {
|
impl Filter {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
let filter = Self::default();
|
||||||
|
// filter.topics = vec![H256::zero().into(); 4];
|
||||||
|
filter
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||||
|
|
|
@ -24,13 +24,15 @@ utils = ["ethers-utils"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ethers-abi = { path = "../ethers-abi", optional = true }
|
ethers-abi = { path = "../ethers-abi", optional = true }
|
||||||
ethers-contract = { path = "../ethers-contract", optional = true }
|
ethers-contract = { path = "../ethers-contract", features = ["abigen"], optional = true }
|
||||||
ethers-providers = { path = "../ethers-providers", optional = true }
|
ethers-providers = { path = "../ethers-providers", optional = true }
|
||||||
ethers-signers = { path = "../ethers-signers", optional = true }
|
ethers-signers = { path = "../ethers-signers", optional = true }
|
||||||
ethers-types = { path = "../ethers-types", optional = true }
|
ethers-types = { path = "../ethers-types", optional = true }
|
||||||
ethers-utils = { path = "../ethers-utils", optional = true }
|
ethers-utils = { path = "../ethers-utils", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
ethers-contract = { path = "../ethers-contract", features = ["abigen"] }
|
||||||
|
|
||||||
anyhow = "1.0.31"
|
anyhow = "1.0.31"
|
||||||
tokio = { version = "0.2.21", features = ["macros"] }
|
tokio = { version = "0.2.21", features = ["macros"] }
|
||||||
serde_json = "1.0.53"
|
serde_json = "1.0.53"
|
||||||
|
|
|
@ -1,68 +1,15 @@
|
||||||
use ethers::{
|
|
||||||
abi::{Detokenize, InvalidOutputType, Token},
|
|
||||||
contract::{Contract, Event, Sender},
|
|
||||||
providers::{HttpProvider, JsonRpcClient},
|
|
||||||
signers::{Client, MainnetWallet, Signer},
|
|
||||||
types::{Address, H256},
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use serde::Serialize;
|
use ethers::{providers::HttpProvider, signers::MainnetWallet, types::Address};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
const ABI: &'static str = r#"[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#;
|
use ethers::contract::abigen;
|
||||||
|
|
||||||
// abigen!(SimpleContract, ABI);
|
// Generate the contract code
|
||||||
|
abigen!(
|
||||||
#[derive(Clone, Debug, Serialize)]
|
SimpleContract,
|
||||||
// TODO: This should be `derive`-able on such types -> similar to how Zexe's Deserialize is done
|
r#"[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","constant": true, "type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#,
|
||||||
struct ValueChanged {
|
event_derives(serde::Deserialize, serde::Serialize)
|
||||||
author: Address,
|
);
|
||||||
old_value: String,
|
|
||||||
new_value: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Detokenize for ValueChanged {
|
|
||||||
fn from_tokens(tokens: Vec<Token>) -> Result<ValueChanged, InvalidOutputType> {
|
|
||||||
let author: Address = tokens[0].clone().to_address().unwrap();
|
|
||||||
let old_value = tokens[1].clone().to_string().unwrap();
|
|
||||||
let new_value = tokens[2].clone().to_string().unwrap();
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
author,
|
|
||||||
old_value,
|
|
||||||
new_value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SimpleContract<'a, S, P>(Contract<'a, S, P>);
|
|
||||||
|
|
||||||
impl<'a, S: Signer, P: JsonRpcClient> SimpleContract<'a, S, P> {
|
|
||||||
fn new<T: Into<Address>>(address: T, client: &'a Client<'a, S, P>) -> Self {
|
|
||||||
let contract = Contract::new(client, serde_json::from_str(&ABI).unwrap(), address.into());
|
|
||||||
Self(contract)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_value<T: Into<String>>(&self, val: T) -> Sender<'a, S, P, H256> {
|
|
||||||
self.0
|
|
||||||
.method("setValue", Some(val.into()))
|
|
||||||
.expect("method not found (this should never happen)")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value_changed<'b>(&'a self) -> Event<'a, 'b, P, ValueChanged>
|
|
||||||
where
|
|
||||||
'a: 'b,
|
|
||||||
{
|
|
||||||
self.0.event("ValueChanged").expect("event does not exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_value(&self) -> Sender<'a, S, P, String> {
|
|
||||||
self.0
|
|
||||||
.method("getValue", None::<()>)
|
|
||||||
.expect("method not found (this should never happen)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
@ -83,7 +30,7 @@ async fn main() -> Result<()> {
|
||||||
let contract = SimpleContract::new(addr, &client);
|
let contract = SimpleContract::new(addr, &client);
|
||||||
|
|
||||||
// call the method
|
// call the method
|
||||||
let _tx_hash = contract.set_value("hi").send().await?;
|
let _tx_hash = contract.set_value("hi".to_owned()).send().await?;
|
||||||
|
|
||||||
let logs = contract.value_changed().from_block(0u64).query().await?;
|
let logs = contract.value_changed().from_block(0u64).query().await?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue