This commit is contained in:
Georgios Konstantopoulos 2020-05-26 21:57:59 +03:00
parent 25f2c5e45d
commit 099fa5d7ef
No known key found for this signature in database
GPG Key ID: FA607837CD26EDBC
33 changed files with 1832 additions and 2284 deletions

165
Cargo.lock generated
View File

@ -1,5 +1,22 @@
# This file is automatically @generated by Cargo.
# 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]]
name = "anyhow"
version = "1.0.31"
@ -114,6 +131,34 @@ dependencies = [
"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]]
name = "dtoa"
version = "0.4.5"
@ -204,11 +249,44 @@ name = "ethers-contract"
version = "0.1.0"
dependencies = [
"ethers-abi 0.1.0",
"ethers-contract-abigen 0.1.0",
"ethers-contract-derive 0.1.0",
"ethers-providers 0.1.0",
"ethers-signers 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)",
"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]]
@ -216,6 +294,7 @@ name = "ethers-providers"
version = "0.1.0"
dependencies = [
"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-utils 0.1.0",
"reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -521,6 +600,17 @@ name = "libc"
version = "0.2.70"
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]]
name = "log"
version = "0.4.8"
@ -602,6 +692,18 @@ name = "openssl-probe"
version = "0.1.2"
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]]
name = "parity-scale-codec"
version = "1.3.0"
@ -646,6 +748,11 @@ name = "pin-utils"
version = "0.1.0"
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]]
name = "ppv-lite86"
version = "0.2.8"
@ -825,6 +932,27 @@ dependencies = [
"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]]
name = "reqwest"
version = "0.10.4"
@ -1021,6 +1149,17 @@ name = "smallvec"
version = "1.4.0"
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]]
name = "spin"
version = "0.5.2"
@ -1059,6 +1198,14 @@ dependencies = [
"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]]
name = "time"
version = "0.1.43"
@ -1200,6 +1347,11 @@ dependencies = [
"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]]
name = "version_check"
version = "0.9.2"
@ -1359,6 +1511,8 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[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 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"
@ -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 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 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 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"
@ -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 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 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 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"
@ -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 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-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 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-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-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 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"
@ -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_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 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 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"
@ -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 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 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 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 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 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 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"
@ -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 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 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 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"

View File

@ -4,7 +4,6 @@ members = [
"./crates/ethers",
"./crates/ethers-abi",
"./crates/ethers-contract",
# "./crates/ethers-derive",
"./crates/ethers-providers",
"./crates/ethers-signers",
"./crates/ethers-types",

View File

@ -11,6 +11,7 @@ Complete Ethereum wallet implementation and utilities in Rust (with WASM and FFI
- [ ] Deploy and interact with smart contracts
- [ ] Type safe smart contract bindings
- [ ] Hardware wallet support
- [ ] CLI for creating transactions, interacting with contracts, generating bindings from ABIs (abigen equivalent), ...
- [ ] ...
## Directory Structure

View File

@ -9,7 +9,7 @@ use thiserror::Error;
#[derive(Clone, Debug, Error)]
#[error("{0}")]
pub struct InvalidOutputType(String);
pub struct InvalidOutputType(pub String);
/// Output type possible to deserialize from Contract ABI
pub trait Detokenize {

View File

@ -6,6 +6,7 @@ edition = "2018"
[dependencies]
ethers-contract-abigen = { path = "ethers-contract-abigen", optional = true }
ethers-contract-derive = { path = "ethers-contract-derive", optional = true }
ethers-abi = { path = "../ethers-abi" }
ethers-providers = { path = "../ethers-providers" }
@ -15,7 +16,8 @@ ethers-types = { path = "../ethers-types" }
serde = { version = "1.0.110", default-features = false }
rustc-hex = { version = "2.1.0", default-features = false }
thiserror = { version = "1.0.19", default-features = false }
once_cell = "1.4.0"
[features]
default = []
abigen = ["ethers-contract-abigen"]
default = ["abigen"]
abigen = ["ethers-contract-abigen", "ethers-contract-derive"]

View File

@ -1,16 +1,14 @@
[package]
name = "ethers-derive"
name = "ethers-contract-abigen"
version = "0.1.0"
authors = ["Nicholas Rodrigues Lordello <nlordell@gmail.com>", "Georgios Konstantopoulos <me@gakonst.com>"]
edition = "2018"
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"
[dependencies]
ethers-abi = { path = "../ethers-abi" }
ethers-abi = { path = "../../ethers-abi" }
ethers-types = { path = "../../ethers-types" }
anyhow = "1.0"
curl = "0.4"
@ -19,3 +17,5 @@ proc-macro2 = "1.0"
quote = "1.0"
syn = "1.0.12"
url = "2.1"
serde_json = "1.0.53"
once_cell = "1.4.0"

View File

@ -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();
}

View File

@ -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,
})
}
}

View File

@ -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()
}
}
}
}

View File

@ -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, &params)
} else {
expand_data_struct(&event_name, &params)
};
// 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(&param.name.to_snake_case())
};
let ty = expand_input_type(&param)?;
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, &params);
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, &params);
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
])
},
);
}
}

View File

@ -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, &param.name);
let kind = types::expand(&param.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, &param.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(&param.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) },
);
}
}

View File

@ -1,11 +1,11 @@
use anyhow::{anyhow, Result};
use ethcontract_common::abi::ParamType;
use ethers_abi::ParamType;
use proc_macro2::{Literal, TokenStream};
use quote::quote;
pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
match kind {
ParamType::Address => Ok(quote! { self::ethcontract::Address }),
ParamType::Address => Ok(quote! { Address }),
ParamType::Bytes => Ok(quote! { Vec<u8> }),
ParamType::Int(n) => match n / 8 {
1 => Ok(quote! { i8 }),
@ -13,7 +13,7 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
3..=4 => Ok(quote! { i32 }),
5..=8 => Ok(quote! { i64 }),
9..=16 => Ok(quote! { i128 }),
17..=32 => Ok(quote! { self::ethcontract::I256 }),
17..=32 => Ok(quote! { I256 }),
_ => Err(anyhow!("unsupported solidity type int{}", n)),
},
ParamType::Uint(n) => match n / 8 {
@ -22,7 +22,7 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
3..=4 => Ok(quote! { u32 }),
5..=8 => Ok(quote! { u64 }),
9..=16 => Ok(quote! { u128 }),
17..=32 => Ok(quote! { self::ethcontract::U256 }),
17..=32 => Ok(quote! { U256 }),
_ => Err(anyhow!("unsupported solidity type uint{}", n)),
},
ParamType::Bool => Ok(quote! { bool }),
@ -43,6 +43,7 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
let size = Literal::usize_unsuffixed(*n);
Ok(quote! { [#inner; #size] })
}
// TODO: Implement abiencoder v2
ParamType::Tuple(_) => Err(anyhow!("ABIEncoderV2 is currently not supported")),
}
}

View File

@ -1,8 +1,10 @@
#![allow(dead_code)]
#![allow(unused_imports)]
#![deny(missing_docs, unsafe_code)]
//! 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.
//! Module for generating type-safe bindings to Ethereum smart contracts. This
//! module is intended to be used either indirectly with the `abigen` procedural
//! macro or directly from a build script / CLI
#[cfg(test)]
#[allow(missing_docs)]
@ -11,14 +13,17 @@
mod test_macros;
mod contract;
use contract::Context;
mod rustfmt;
mod source;
mod util;
pub use crate::source::Source;
pub use crate::util::parse_address;
pub use ethers_types::Address;
pub use source::Source;
pub use util::parse_address;
use anyhow::Result;
pub use ethcontract_common::Address;
use proc_macro2::TokenStream;
use std::collections::HashMap;
use std::fs::File;
@ -28,22 +33,26 @@ use std::path::Path;
/// Internal global arguments passed to the generators for each individual
/// component that control expansion.
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.
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.
runtime_crate_name: String,
/// The visibility modifier to use for the generated module and contract
/// re-export.
visibility_modifier: Option<String>,
/// Override the contract module name that contains the generated code.
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.
method_aliases: HashMap<String, String>,
/// Derives added to event structs and enums.
event_derives: Vec<String>,
}
@ -51,14 +60,14 @@ pub(crate) struct Args {
impl Args {
/// Creates a new builder given the path to a contract's truffle artifact
/// JSON file.
pub fn new(source: Source) -> Self {
pub fn new(contract_name: &str, abi_source: Source) -> Self {
Args {
artifact_source: source,
runtime_crate_name: "ethcontract".to_owned(),
abi_source,
contract_name: contract_name.to_owned(),
runtime_crate_name: "abigen".to_owned(),
visibility_modifier: None,
contract_mod_override: None,
contract_name_override: None,
deployments: HashMap::new(),
method_aliases: HashMap::new(),
event_derives: Vec::new(),
}
@ -88,35 +97,39 @@ pub struct Builder {
}
impl Builder {
/// Creates a new builder given the path to a contract's truffle artifact
/// JSON file.
pub fn new<P>(artifact_path: P) -> Self
/// Creates a new builder given the contract's ABI JSON string
pub fn from_str(name: &str, abi: &str) -> 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
P: AsRef<Path>,
{
Builder::with_source(Source::local(artifact_path))
Builder::source(name, Source::local(path))
}
/// 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
S: AsRef<str>,
{
let source = Source::parse(source_url)?;
Ok(Builder::with_source(source))
let source = Source::parse(url)?;
Ok(Builder::source(name, source))
}
/// Creates a new builder with the given artifact JSON source.
pub fn with_source(source: Source) -> Self {
/// Creates a new builder with the given ABI JSON source.
pub fn source(name: &str, source: Source) -> Self {
Builder {
args: Args::new(source),
args: Args::new(name, source),
options: SerializationOptions::default(),
}
}
/// Sets the crate name for the runtime crate. This setting is usually only
/// 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
S: Into<String>,
{
@ -126,7 +139,7 @@ impl Builder {
/// Sets an optional visibility modifier for the generated module and
/// 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
S: Into<String>,
{
@ -135,7 +148,7 @@ impl Builder {
}
/// 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
S: Into<String>,
{
@ -143,48 +156,6 @@ impl Builder {
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
/// will be in Rust. For solidity methods without an alias, the snake cased
/// method name will be used.
@ -204,7 +175,7 @@ impl Builder {
///
/// Note that in case `rustfmt` does not exist or produces an error, the
/// 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
}
@ -232,7 +203,7 @@ impl Builder {
/// Generates the contract bindings.
pub fn generate(self) -> Result<ContractBindings> {
let tokens = contract::expand(self.args)?;
let tokens = Context::expand(self.args)?;
Ok(ContractBindings {
tokens,
options: self.options,

View File

@ -1,48 +1,54 @@
//! 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 ethcontract_common::Address;
use std::borrow::Cow;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::{
borrow::Cow,
env, fs,
path::{Path, PathBuf},
str::FromStr,
};
use url::Url;
/// A source of a Truffle artifact JSON.
#[derive(Clone, Debug, Eq, PartialEq)]
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),
/// A truffle artifact or ABI to be retrieved over HTTP(S).
/// An ABI to be retrieved over HTTP(S).
Http(Url),
/// An address of a mainnet contract that has been verified on Etherscan.io.
Etherscan(Address),
/// The package identifier of an npm package with a path to a Truffle
/// artifact or ABI to be retrieved from `unpkg.io`.
Npm(String),
}
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
/// from `etherscan.io`, this method parses artifact source URLs and accepts
/// Contract ABIs can be retrieved from the local filesystem or online
/// from `etherscan.io`, this method parses ABI source URLs and accepts
/// the following:
/// - `relative/path/to/Contract.json`: a relative path to a truffle
/// artifact JSON file. This relative path is rooted in the current
/// working directory. To specify the root for relative paths, use
/// `Source::with_root`.
/// - `relative/path/to/Contract.json`: a relative path to an ABI JSON file.
/// This relative path is rooted in the current working directory.
/// To specify the root for relative paths, use `Source::with_root`.
/// - `/absolute/path/to/Contract.json` or
/// `file:///absolute/path/to/Contract.json`: an absolute path or file URL
/// to a truffle artifact JSON file.
/// - `http(s)://...` an HTTP url to a contract ABI or Truffle artifact.
/// to an ABI JSON file.
/// - `http(s)://...` an HTTP url to a contract ABI.
/// - `etherscan:0xXX..XX` or `https://etherscan.io/address/0xXX..XX`: a
/// address or URL of a verified contract on Etherscan.
/// - `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
/// `index.js`). The contract artifact or ABI will be retrieved through
/// `index.js`). The contract ABI will be retrieved through
/// `unpkg.io`.
pub fn parse<S>(source: S) -> Result<Self>
where
@ -118,12 +124,13 @@ impl Source {
/// 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
/// dependending on the source type.
pub fn artifact_json(&self) -> Result<String> {
pub fn get(&self) -> Result<String> {
match self {
Source::Local(path) => get_local_contract(path),
Source::Http(url) => get_http_contract(url),
Source::Etherscan(address) => get_etherscan_contract(*address),
Source::Npm(package) => get_npm_contract(package),
Source::String(abi) => Ok(abi.clone()),
}
}
}

View File

@ -1,6 +1,7 @@
use ethers_types::Address;
use anyhow::{anyhow, Result};
use curl::easy::Easy;
use ethcontract_common::Address;
use inflector::Inflector;
use proc_macro2::{Ident, Literal, Span, TokenStream};
use quote::quote;

View File

@ -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() {}

View File

@ -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"

View File

@ -1,132 +1,40 @@
//! Implementation of procedural macro for generating type-safe bindings to an
//! 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 quote::{quote, ToTokens as _};
use std::collections::HashSet;
use std::error::Error;
use syn::ext::IdentExt;
use syn::parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult};
use syn::{
braced, parenthesized, parse_macro_input, Error as SynError, Ident, LitInt, LitStr, Path,
Token, Visibility,
};
use syn::{braced, parenthesized, Ident, LitStr, Path, Token, Visibility};
// TODO: Make it accept an inline ABI array
/// 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>> {
pub(crate) fn expand(args: ContractArgs) -> Result<TokenStream2, Box<dyn Error>> {
Ok(args.into_builder()?.generate()?.into_tokens())
}
/// Contract procedural macro arguments.
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
struct ContractArgs {
pub(crate) struct ContractArgs {
visibility: Option<String>,
artifact_path: String,
name: String,
path: String,
parameters: Vec<Parameter>,
}
impl ContractArgs {
fn into_builder(self) -> Result<Builder, Box<dyn Error>> {
let mut builder = Builder::from_source_url(&self.artifact_path)?
.with_visibility_modifier(self.visibility);
let mut builder =
Builder::from_str(&self.name, &self.path).visibility_modifier(self.visibility);
for parameter in self.parameters.into_iter() {
builder = match parameter {
Parameter::Mod(name) => builder.with_contract_mod_override(Some(name)),
Parameter::Contract(name) => builder.with_contract_name_override(Some(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::Mod(name) => builder.contract_mod_override(Some(name)),
Parameter::Crate(name) => builder.runtime_crate_name(name),
Parameter::Methods(methods) => methods.into_iter().fold(builder, |builder, m| {
builder.add_method_alias(m.signature, m.alias)
}),
@ -142,17 +50,24 @@ impl ContractArgs {
impl ParseInner for ContractArgs {
fn spanned_parse(input: ParseStream) -> ParseResult<(Span, Self)> {
// read the visibility parameter
let visibility = match input.parse::<Visibility>()? {
Visibility::Inherited => None,
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
// 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
// directory. Eventually we can use the `Span::source_file` API to
// have a better experience.
let (span, artifact_path) = {
let (span, path) = {
let literal = input.parse::<LitStr>()?;
(literal.span(), literal.value())
};
@ -160,6 +75,7 @@ impl ParseInner for ContractArgs {
if !input.is_empty() {
input.parse::<Token![,]>()?;
}
let parameters = input
.parse_terminated::<_, Token![,]>(Parameter::parse)?
.into_iter()
@ -169,7 +85,8 @@ impl ParseInner for ContractArgs {
span,
ContractArgs {
visibility,
artifact_path,
name,
path,
parameters,
},
))
@ -180,9 +97,7 @@ impl ParseInner for ContractArgs {
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
enum Parameter {
Mod(String),
Contract(String),
Crate(String),
Deployments(Vec<Deployment>),
Methods(Vec<Method>),
EventDerives(Vec<String>),
}
@ -201,35 +116,6 @@ impl Parse for Parameter {
let name = input.parse::<Ident>()?.to_string();
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" => {
let content;
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.
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
struct Method {
@ -324,7 +187,7 @@ impl Parse for Method {
.parse_terminated::<_, Token![,]>(Ident::parse)?
.iter()
.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))?;
Ok(Param {
name: "".into(),
@ -425,10 +288,6 @@ mod tests {
crate = foobar,
mod = contract,
contract = Contract,
deployments {
1 => "0x000102030405060708090a0b0c0d0e0f10111213",
4 => "0x0123456789012345678901234567890123456789",
},
methods {
myMethod(uint256, bool) as my_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]
fn duplicate_method_rename_error() {
contract_args_err!(

View File

@ -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()
}

View File

@ -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()
}

View File

@ -1,284 +1,15 @@
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,
};
mod contract;
pub use contract::*;
use rustc_hex::ToHex;
use std::{collections::HashMap, fmt::Debug, hash::Hash};
use thiserror::Error as ThisError;
#[cfg(feature = "abigen")]
pub use ethers_contract_abigen::Builder;
/// 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: Abi,
address: Address,
#[cfg(feature = "abigen")]
pub use ethers_contract_derive::abigen;
/// 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)>,
}
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()
}
// re-export for convenience
pub use ethers_abi as abi;
pub use ethers_providers as providers;
pub use ethers_signers as signers;
pub use ethers_types as types;
pub use once_cell::sync::Lazy;

View File

@ -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;
})
}

View File

@ -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])
},
);
}
}

View File

@ -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))
}
}
})
}

View File

@ -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, &params)
} else {
expand_data_struct(&event_name, &params)
};
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(&param.name.to_snake_case())
};
let ty = expand_input_type(&param)?;
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, &params);
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, &params);
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
])
},
);
}
}

View File

@ -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, &param.name);
let kind = types::expand(&param.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, &param.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(&param.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) },
);
}
}

View File

@ -89,7 +89,9 @@ pub struct Filter {
impl Filter {
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 {

View File

@ -24,13 +24,15 @@ utils = ["ethers-utils"]
[dependencies]
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-signers = { path = "../ethers-signers", optional = true }
ethers-types = { path = "../ethers-types", optional = true }
ethers-utils = { path = "../ethers-utils", optional = true }
[dev-dependencies]
ethers-contract = { path = "../ethers-contract", features = ["abigen"] }
anyhow = "1.0.31"
tokio = { version = "0.2.21", features = ["macros"] }
serde_json = "1.0.53"

View File

@ -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 serde::Serialize;
use ethers::{providers::HttpProvider, signers::MainnetWallet, types::Address};
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);
#[derive(Clone, Debug, Serialize)]
// TODO: This should be `derive`-able on such types -> similar to how Zexe's Deserialize is done
struct ValueChanged {
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)")
}
}
// Generate the contract code
abigen!(
SimpleContract,
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"}]"#,
event_derives(serde::Deserialize, serde::Serialize)
);
#[tokio::main]
async fn main() -> Result<()> {
@ -83,7 +30,7 @@ async fn main() -> Result<()> {
let contract = SimpleContract::new(addr, &client);
// 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?;