From af0fcdfb7c51bf699903a09674026ce8e13430cc Mon Sep 17 00:00:00 2001 From: starkbamse <139136798+starkbamse@users.noreply.github.com> Date: Thu, 9 May 2024 00:47:09 +0200 Subject: [PATCH] Ported to alloy, deprecate rust-web3, treasury transfer - Deprecated rust-web3 in favor of alloy-rs due to rust-web3 being deprecated in the future - Replaced eth abi with IERC20 abi. - Transfer paid invoice funds to treasury when paid TODO: in the near future token transfers also need to be included so that it is possible to configure payments via tokens. --- Cargo.lock | 537 ++++------------------------- Cargo.toml | 4 +- src/abi/IERC20.json | 279 +++++++++++++++ src/erc20/abi.rs | 1 - src/erc20/mod.rs | 63 ++-- src/gateway/mod.rs | 69 +++- src/lib.rs | 18 +- src/poller/mod.rs | 84 +++-- src/transfers/errors.rs | 11 + src/transfers/gas_transfers/mod.rs | 99 ++++++ src/transfers/mod.rs | 37 ++ src/types/mod.rs | 8 +- 12 files changed, 651 insertions(+), 559 deletions(-) create mode 100644 src/abi/IERC20.json delete mode 100644 src/erc20/abi.rs create mode 100644 src/transfers/errors.rs create mode 100644 src/transfers/gas_transfers/mod.rs create mode 100644 src/transfers/mod.rs diff --git a/Cargo.lock b/Cargo.lock index bddbf44..7f151c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,13 +8,13 @@ version = "0.1.0" dependencies = [ "alloy", "bincode", + "reqwest", "serde", "sha2", "sled", "thiserror", "tokio", "uuid", - "web3", ] [[package]] @@ -44,15 +44,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "allocator-api2" version = "0.2.18" @@ -65,16 +56,20 @@ version = "0.1.0" source = "git+https://github.com/alloy-rs/alloy#35cbf35164f31d2de1b84b2a8a9986e5b9b1560f" dependencies = [ "alloy-consensus", + "alloy-contract", "alloy-core", "alloy-eips", "alloy-genesis", + "alloy-network", "alloy-provider", "alloy-rpc-client", + "alloy-rpc-types", "alloy-serde", "alloy-signer", "alloy-signer-wallet", + "alloy-transport", "alloy-transport-http", - "reqwest 0.12.4", + "reqwest", ] [[package]] @@ -90,6 +85,24 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-contract" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy#35cbf35164f31d2de1b84b2a8a9986e5b9b1560f" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-sol-types", + "alloy-transport", + "futures", + "futures-util", + "thiserror", +] + [[package]] name = "alloy-core" version = "0.7.2" @@ -228,7 +241,7 @@ dependencies = [ "futures", "futures-utils-wasm", "lru", - "reqwest 0.12.4", + "reqwest", "serde_json", "tokio", "tracing", @@ -267,7 +280,7 @@ dependencies = [ "alloy-transport-http", "futures", "pin-project", - "reqwest 0.12.4", + "reqwest", "serde", "serde_json", "tokio", @@ -351,6 +364,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89c80a2cb97e7aa48611cbb63950336f9824a174cdf670527cc6465078a26ea1" dependencies = [ + "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck 0.4.1", @@ -369,11 +383,13 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c58894b58ac50979eeac6249661991ac40b9d541830d9a725f7714cc9ef08c23" dependencies = [ + "alloy-json-abi", "const-hex", "dunce", "heck 0.5.0", "proc-macro2", "quote", + "serde_json", "syn 2.0.60", "syn-solidity", ] @@ -393,6 +409,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "399287f68d1081ed8b1f4903c49687658b95b142207d7cb4ae2f4813915343ef" dependencies = [ + "alloy-json-abi", "alloy-primitives", "alloy-sol-macro", "const-hex", @@ -405,7 +422,7 @@ version = "0.1.0" source = "git+https://github.com/alloy-rs/alloy#35cbf35164f31d2de1b84b2a8a9986e5b9b1560f" dependencies = [ "alloy-json-rpc", - "base64 0.22.1", + "base64", "futures-util", "futures-utils-wasm", "serde", @@ -424,7 +441,7 @@ source = "git+https://github.com/alloy-rs/alloy#35cbf35164f31d2de1b84b2a8a9986e5 dependencies = [ "alloy-json-rpc", "alloy-transport", - "reqwest 0.12.4", + "reqwest", "serde_json", "tower", "tracing", @@ -632,18 +649,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -704,15 +709,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -951,7 +947,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", + "block-buffer", "const-oid", "crypto-common", "subtle", @@ -1027,50 +1023,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "ethabi" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" -dependencies = [ - "ethereum-types", - "hex", - "once_cell", - "regex", - "serde", - "serde_json", - "sha3", - "thiserror", - "uint", -] - -[[package]] -name = "ethbloom" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" -dependencies = [ - "crunchy", - "fixed-hash", - "impl-rlp", - "impl-serde", - "tiny-keccak", -] - -[[package]] -name = "ethereum-types" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" -dependencies = [ - "ethbloom", - "fixed-hash", - "impl-rlp", - "impl-serde", - "primitive-types", - "uint", -] - [[package]] name = "fastrand" version = "2.1.0" @@ -1227,12 +1179,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" - [[package]] name = "futures-util" version = "0.3.30" @@ -1313,16 +1259,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.26" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http 0.2.12", + "http", "indexmap", "slab", "tokio", @@ -1340,30 +1286,6 @@ dependencies = [ "allocator-api2", ] -[[package]] -name = "headers" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" -dependencies = [ - "base64 0.21.7", - "bytes", - "headers-core", - "http 0.2.12", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http 0.2.12", -] - [[package]] name = "heck" version = "0.4.1" @@ -1406,17 +1328,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.1.0" @@ -1428,17 +1339,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.0" @@ -1446,7 +1346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http 1.1.0", + "http", ] [[package]] @@ -1457,8 +1357,8 @@ checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", "futures-core", - "http 1.1.0", - "http-body 1.0.0", + "http", + "http-body", "pin-project-lite", ] @@ -1468,36 +1368,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "0.14.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.3.1" @@ -1507,8 +1377,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "h2", + "http", + "http-body", "httparse", "itoa", "pin-project-lite", @@ -1517,19 +1388,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper 0.14.28", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "hyper-tls" version = "0.6.0" @@ -1538,7 +1396,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.3.1", + "hyper", "hyper-util", "native-tls", "tokio", @@ -1555,9 +1413,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", - "http-body 1.0.0", - "hyper 1.3.1", + "http", + "http-body", + "hyper", "pin-project-lite", "socket2", "tokio", @@ -1566,16 +1424,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.5.0" @@ -1595,24 +1443,6 @@ dependencies = [ "parity-scale-codec", ] -[[package]] -name = "impl-rlp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" -dependencies = [ - "rlp", -] - -[[package]] -name = "impl-serde" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" -dependencies = [ - "serde", -] - [[package]] name = "impl-trait-for-tuples" version = "0.2.2" @@ -1682,21 +1512,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonrpc-core" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" -dependencies = [ - "futures", - "futures-executor", - "futures-util", - "log", - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "k256" version = "0.13.3" @@ -1710,15 +1525,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - [[package]] name = "keccak-asm" version = "0.1.0" @@ -1883,12 +1689,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "openssl" version = "0.10.64" @@ -1970,16 +1770,6 @@ dependencies = [ "parking_lot_core 0.8.6", ] -[[package]] -name = "parking_lot" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" -dependencies = [ - "lock_api", - "parking_lot_core 0.9.10", -] - [[package]] name = "parking_lot_core" version = "0.8.6" @@ -2092,8 +1882,6 @@ checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", - "impl-rlp", - "impl-serde", "uint", ] @@ -2238,90 +2026,29 @@ dependencies = [ "bitflags 2.5.0", ] -[[package]] -name = "regex" -version = "1.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - [[package]] name = "regex-syntax" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.28", - "hyper-tls 0.5.0", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg 0.50.0", -] - [[package]] name = "reqwest" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64 0.22.1", + "base64", "bytes", + "encoding_rs", "futures-core", "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "h2", + "http", + "http-body", "http-body-util", - "hyper 1.3.1", - "hyper-tls 0.6.0", + "hyper", + "hyper-tls", "hyper-util", "ipnet", "js-sys", @@ -2331,11 +2058,12 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 2.1.2", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", + "system-configuration", "tokio", "tokio-native-tls", "tower-service", @@ -2343,7 +2071,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg 0.52.0", + "winreg", ] [[package]] @@ -2439,22 +2167,13 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pemfile" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.22.1", + "base64", "rustls-pki-types", ] @@ -2511,24 +2230,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" -dependencies = [ - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" -dependencies = [ - "cc", -] - [[package]] name = "security-framework" version = "2.10.0" @@ -2619,30 +2320,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - [[package]] name = "sha2" version = "0.10.8" @@ -2654,16 +2331,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest 0.10.7", - "keccak", -] - [[package]] name = "sha3-asm" version = "0.1.0" @@ -2674,15 +2341,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - [[package]] name = "signature" version = "2.2.0" @@ -2715,7 +2373,7 @@ dependencies = [ "fxhash", "libc", "log", - "parking_lot 0.11.2", + "parking_lot", ] [[package]] @@ -2734,21 +2392,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "soketto" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" -dependencies = [ - "base64 0.13.1", - "bytes", - "futures", - "httparse", - "log", - "rand", - "sha-1", -] - [[package]] name = "spki" version = "0.7.3" @@ -2913,10 +2556,7 @@ dependencies = [ "bytes", "libc", "mio", - "num_cpus", - "parking_lot 0.12.2", "pin-project-lite", - "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.48.0", @@ -2963,7 +2603,6 @@ checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", - "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -3111,7 +2750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna", "percent-encoding", ] @@ -3242,54 +2881,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web3" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5388522c899d1e1c96a4c307e3797e0f697ba7c77dd8e0e625ecba9dd0342937" -dependencies = [ - "arrayvec", - "base64 0.21.7", - "bytes", - "derive_more", - "ethabi", - "ethereum-types", - "futures", - "futures-timer", - "headers", - "hex", - "idna 0.4.0", - "jsonrpc-core", - "log", - "once_cell", - "parking_lot 0.12.2", - "pin-project", - "reqwest 0.11.27", - "rlp", - "secp256k1", - "serde", - "serde_json", - "soketto", - "tiny-keccak", - "tokio", - "tokio-stream", - "tokio-util", - "url", - "web3-async-native-tls", -] - -[[package]] -name = "web3-async-native-tls" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f6d8d1636b2627fe63518d5a9b38a569405d9c9bc665c43c9c341de57227ebb" -dependencies = [ - "native-tls", - "thiserror", - "tokio", - "url", -] - [[package]] name = "winapi" version = "0.3.9" @@ -3469,16 +3060,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "winreg" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 42c2eac..2bbfffe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,12 +6,12 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -alloy = { git = "https://github.com/alloy-rs/alloy", version = "0.1.0",features=["signers","signer-wallet"] } +alloy = { git = "https://github.com/alloy-rs/alloy", version = "0.1.0",features=["rpc-types-eth","eips","signers","signer-wallet","consensus","network","providers","transports","transport-http","contract"] } bincode = "1.3.3" sled = "0.34.7" thiserror = "1.0.59" -web3 = "0.19.0" serde = { version="1.0.197",features=["derive"] } sha2 = "0.10.8" tokio = "1.37.0" uuid = {version="1.8.0",features=["v4"]} +reqwest = "0.12.4" diff --git a/src/abi/IERC20.json b/src/abi/IERC20.json new file mode 100644 index 0000000..37f31f9 --- /dev/null +++ b/src/abi/IERC20.json @@ -0,0 +1,279 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "guy", + "type": "address" + }, + { + "name": "wad", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "src", + "type": "address" + }, + { + "name": "dst", + "type": "address" + }, + { + "name": "wad", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "wad", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "dst", + "type": "address" + }, + { + "name": "wad", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "deposit", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "src", + "type": "address" + }, + { + "indexed": true, + "name": "guy", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "src", + "type": "address" + }, + { + "indexed": true, + "name": "dst", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "dst", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "src", + "type": "address" + }, + { + "indexed": false, + "name": "wad", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + } +] \ No newline at end of file diff --git a/src/erc20/abi.rs b/src/erc20/abi.rs deleted file mode 100644 index 5149670..0000000 --- a/src/erc20/abi.rs +++ /dev/null @@ -1 +0,0 @@ -pub const ERC20_ABI: &str = r#"[{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"constant":true,"inputs":[],"name":"_decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]"#; diff --git a/src/erc20/mod.rs b/src/erc20/mod.rs index dbe3c1e..3c961ae 100644 --- a/src/erc20/mod.rs +++ b/src/erc20/mod.rs @@ -1,58 +1,57 @@ -mod abi; -use web3::{ - contract::{Contract, Options}, - transports::Http, - types::{Address, U256}, - Web3, +use alloy::{ + contract::Error, primitives::Uint, providers::RootProvider, sol, transports::http::Http, }; +use reqwest::Client; -use std::str::FromStr; +use self::IERC20::IERC20Instance; + +sol!( + #[allow(missing_docs)] + #[sol(rpc)] + IERC20, + "src/abi/IERC20.json" +); -use self::abi::ERC20_ABI; #[derive(Clone)] pub struct ERC20Token { - contract: Contract, + pub contract: IERC20Instance, RootProvider>>, } impl ERC20Token { /// Creates a new instance of an ERC20 token. This is just a wrapper /// function to simplify the interactions with contracts. - pub fn new(web3: Web3, token_address: String) -> ERC20Token { - let contract = Contract::from_json( - web3.eth(), - token_address.parse().unwrap(), - ERC20_ABI.as_bytes(), - ) - .unwrap(); + pub fn new(provider: RootProvider>, token_address: String) -> ERC20Token { + let contract = IERC20::new(token_address.parse().unwrap(), provider); ERC20Token { contract } } /// Retrieves the token balance of a specified address - pub async fn get_balance(&self, address: String) -> Result { - self.contract - .query( - "balanceOf", - Address::from_str(&address).unwrap(), - None, - Options::default(), - None, - ) - .await + pub async fn get_balance(&self, address: String) -> Result, Error> { + let IERC20::balanceOfReturn { _0 } = self + .contract + .balanceOf(address.parse().unwrap()) + .call() + .await?; + Ok(_0) } } #[cfg(test)] mod tests { - use web3::{transports::Http, types::U256, Web3}; - use crate::erc20::ERC20Token; + use std::str::FromStr; + use alloy::{primitives::U256, providers::ProviderBuilder}; + use reqwest::Url; + + use crate::erc20::ERC20Token; #[tokio::test] async fn valid_balance() { - let http = Http::new("https://bsc-dataseed1.binance.org/").unwrap(); - let web3 = Web3::new(http); + let provider = ProviderBuilder::new() + .on_http(Url::from_str("https://bsc-dataseed1.binance.org/").unwrap()); + let token = ERC20Token::new( - web3, + provider, "0x2170ed0880ac9a755fd29b2688956bd959f933f8".to_string(), ); let balance = token @@ -60,6 +59,6 @@ mod tests { .await .unwrap(); println!("Balance check: {}", balance); - assert!(balance.ge(&U256::zero())); + assert!(balance.ge(&U256::from_str("0").unwrap())); } } diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index f7a1078..5101af0 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,10 +1,15 @@ mod hash; -use std::{future::Future, pin::Pin, sync::Arc}; +use std::{future::Future, pin::Pin, str::FromStr, sync::Arc}; -use alloy::signers::wallet::LocalWallet; +use alloy::{ + primitives::{Address, U256}, + providers::{ProviderBuilder, RootProvider}, + signers::wallet::LocalWallet, + transports::http::Http, +}; +use reqwest::{Client, Url}; use sled::Tree; use tokio::sync::Mutex; -use web3::{transports::Http, types::U256, Web3}; use crate::{ common::{get_unix_time_millis, get_unix_time_seconds, DatabaseError}, @@ -18,18 +23,25 @@ use self::hash::hash_now; /// ## AcceptEVM /// /// -/// The payment gateway is designed to be ran on the main thread, majority of +/// The payment gateway is designed to be ran on the main thread, all of /// the functions are non-blocking asynchronous functions. The underlying polling -/// mechanism is offloaded using `tokio::spawn`` +/// mechanism is offloaded using `tokio::spawn`. #[derive(Clone)] pub struct PaymentGateway { - pub web3: Web3, - pub invoice_delay_millis: u64, - pub callback: AsyncCallback, + pub config: PaymentGatewayConfiguration, pub tree: Tree, pub name: String, } +#[derive(Clone)] +pub struct PaymentGatewayConfiguration { + pub provider: RootProvider>, + pub treasury_address: Address, + pub invoice_delay_millis: u64, + pub callback: AsyncCallback, + pub transfer_gas_limit: Option, +} + // Type alias for the underlying Web3 type. pub type Wei = U256; @@ -40,19 +52,26 @@ pub type AsyncCallback = impl PaymentGateway { /// Creates a new payment gateway. /// - /// - **rpc_url**: the HTTP Rpc url of the EVM network - /// - **invoice_delay_millis**: how long to wait before checking the next invoice in milliseconds. + /// - `rpc_url`: the HTTP Rpc url of the EVM network + /// - `treasury_address`: the address of the treasury for all paid invoices, on this EVM network. + /// - `invoice_delay_millis`: how long to wait before checking the next invoice in milliseconds. /// This is used to prevent potential rate limits from the node. - /// - **callback**: an async function that is called when an invoice is paid. - /// - **sled_path**: the path of the sled database where the pending invoices will + /// - `callback`: an async function that is called when an invoice is paid. + /// - `sled_path`: The path of the sled database where the pending invoices will /// be stored. In the event of a crash the invoices are saved and will be /// checked on reboot. + /// - `name`: A name that describes this gateway. Perhaps the EVM network used? + /// - `transfer_gas_limit`: An optional gas limit used when transferring gas from paid invoices to + /// the treasury. Useful in case your treasury address is a contract address + /// that implements custom functionality for handling incoming gas. pub fn new( rpc_url: &str, + treasury_address: String, invoice_delay_millis: u64, callback: F, sled_path: &str, name: String, + transfer_gas_limit: Option, ) -> PaymentGateway where F: Fn(Invoice) -> Fut + 'static + Send + Sync, @@ -63,7 +82,7 @@ impl PaymentGateway { let db = sled::open(sled_path).unwrap(); let tree = db.open_tree("invoices").unwrap(); - let http = Http::new(rpc_url).unwrap(); + let provider = ProviderBuilder::new().on_http(Url::from_str(rpc_url).unwrap()); // Wrap the callback in Arc> to allow sharing across threads and state mutation // We have to create a pinned box to prevent the future from being moved around in heap memory. @@ -71,10 +90,18 @@ impl PaymentGateway { Box::pin(callback(invoice)) as Pin + Send>> })); + // TODO: When implementing token transfers allow the user to add their gas wallet here. + PaymentGateway { - web3: Web3::new(http), - invoice_delay_millis, - callback, + config: PaymentGatewayConfiguration { + provider, + treasury_address: treasury_address + .parse() + .unwrap_or_else(|_| panic!("Invalid treasury address")), + invoice_delay_millis, + callback, + transfer_gas_limit, + }, tree, name, } @@ -120,19 +147,23 @@ impl PaymentGateway { message: Vec, expires_in_seconds: u64, ) -> Result { + // Generate random wallet let signer = LocalWallet::random(); - let address = signer.address(); let invoice = Invoice { - to: address.to_string(), + to: signer.address().to_string(), + wallet: signer.to_bytes(), amount, method, message, paid_at_timestamp: 0, expires: get_unix_time_seconds() + expires_in_seconds, + receipt: None, }; + + // Create collision-safe key for the map let seed = format!("{}{}", signer.address(), get_unix_time_millis()); let invoice_id = hash_now(seed); - + // Save the invoice in db. set::(&self.tree, &invoice_id, invoice.clone()).await?; Ok(invoice) } diff --git a/src/lib.rs b/src/lib.rs index 3f7f0fd..d38b92a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,12 +4,14 @@ mod db; mod erc20; pub mod gateway; mod poller; +mod transfers; pub mod types; #[cfg(test)] mod tests { - use std::{fs, path::Path}; - use web3::types::U256; + use std::{fs, path::Path, str::FromStr}; + + use alloy::primitives::U256; use crate::{ common::DatabaseError, @@ -19,7 +21,15 @@ mod tests { fn setup_test_gateway(db_path: &str) -> PaymentGateway { async fn callback(_invoice: Invoice) {} - PaymentGateway::new("https://123.com", 10, callback, db_path, "test".to_string()) + PaymentGateway::new( + "https://123.com", + "0x0".to_string(), + 10, + callback, + db_path, + "test".to_string(), + Some(21000), + ) } fn remove_test_db(db_path: &str) { @@ -31,7 +41,7 @@ mod tests { async fn insert_test_invoice(gateway: &PaymentGateway) -> Result { gateway .new_invoice( - U256::one(), + U256::from_str("0").unwrap(), PaymentMethod { is_native: true, token_address: None, diff --git a/src/poller/mod.rs b/src/poller/mod.rs index 2020c33..e33e33d 100644 --- a/src/poller/mod.rs +++ b/src/poller/mod.rs @@ -1,21 +1,28 @@ -use sled::Tree; -use web3::{transports::Http, types::U256, Web3}; - use crate::{ audit::log_sync, common::get_unix_time_seconds, db::{delete, get_all}, erc20::ERC20Token, gateway::PaymentGateway, + transfers::{errors::TransferError, gas_transfers::transfer_gas_to_treasury}, types::Invoice, }; +use alloy::{ + primitives::Uint, + providers::{Provider, RootProvider}, + rpc::types::eth::TransactionReceipt, + transports::http::Http, +}; +use reqwest::Client; +use sled::Tree; + /// Checks if a specific token of a specific amount has been received /// at a certain address. async fn check_if_token_received( token: ERC20Token, invoice: Invoice, -) -> Result { +) -> Result { let balance_of_recipient = token.get_balance(invoice.to).await?; if balance_of_recipient.ge(&invoice.amount) { return Ok(true); @@ -24,13 +31,21 @@ async fn check_if_token_received( } /// Retrieves the gas token balance of the specified address on the specified web3 instance -async fn get_native_balance(web3: Web3, address: String) -> Result { - web3.eth().balance(address.parse().unwrap(), None).await +async fn get_native_balance( + provider: RootProvider>, + address: String, +) -> Result, alloy::contract::Error> { + Ok(provider + .get_balance(address.parse().unwrap(), alloy::eips::BlockId::latest()) + .await?) } /// Used to check if the invoice recipient has received enough money to cover the invoice -async fn check_if_native_received(web3: Web3, invoice: Invoice) -> Result { - let balance_of_recipient = get_native_balance(web3, invoice.to).await?; +async fn check_if_native_received( + provider: RootProvider>, + invoice: Invoice, +) -> Result { + let balance_of_recipient = get_native_balance(provider, invoice.to).await?; if balance_of_recipient.ge(&invoice.amount) { return Ok(true); } @@ -39,10 +54,10 @@ async fn check_if_native_received(web3: Web3, invoice: Invoice) -> Result< /// A function that branches control flow depending on the invoice shall /// be paid by an ERC20-compatible token or the native gas token on the network -async fn check_and_process(web3: Web3, invoice: Invoice) -> bool { +async fn check_and_process(provider: RootProvider>, invoice: Invoice) -> bool { match invoice.clone().method.token_address { Some(address) => { - let token = ERC20Token::new(web3, address); + let token = ERC20Token::new(provider, address); match check_if_token_received(token, invoice).await { Ok(result) => result, Err(error) => { @@ -51,7 +66,7 @@ async fn check_and_process(web3: Web3, invoice: Invoice) -> bool { } } } - None => match check_if_native_received(web3, invoice).await { + None => match check_if_native_received(provider, invoice).await { Ok(result) => result, Err(error) => { log_sync(&format!("Failed to check balance: {}", error)); @@ -74,6 +89,13 @@ async fn delete_invoice(tree: &Tree, key: String) { } } +async fn transfer_to_treasury( + gateway: PaymentGateway, + invoice: Invoice, +) -> Result { + transfer_gas_to_treasury(gateway, invoice).await +} + /// Periodically checks if invoices are paid in accordance /// to the specified polling interval. pub async fn poll_payments(gateway: PaymentGateway) { @@ -81,7 +103,7 @@ pub async fn poll_payments(gateway: PaymentGateway) { match get_all::(&gateway.tree).await { Ok(all) => { // Loop through all invoices - for entry in all { + for mut entry in all { // If the current time is greater than expiry if get_unix_time_seconds() > entry.1.expires { // Delete the invoice and continue with the next iteration @@ -90,19 +112,33 @@ pub async fn poll_payments(gateway: PaymentGateway) { } // Check if the invoice was paid let check_result = - check_and_process(gateway.web3.clone(), entry.clone().1).await; + check_and_process(gateway.config.provider.clone(), entry.clone().1).await; + if check_result { - // If the invoice was paid, delete it, stand in queue for the + // Attempt transfer to treasury + match transfer_to_treasury(gateway.clone(), entry.1.clone()).await { + Ok(receipt) => { + entry.1.receipt = Some(receipt); + } + Err(error) => { + log_sync(&format!( + "Could not transfer paid invoice to treasury: {}", + error + )); + } + } + + // If the transfer_to_treasury invoice was paid, delete it, stand in queue for the // lock to the callback function. delete_invoice(&gateway.tree, entry.0).await; let mut invoice = entry.1; invoice.paid_at_timestamp = get_unix_time_seconds(); - let lock = gateway.callback.lock().await; + let lock = gateway.config.callback.lock().await; (*lock)(invoice).await; // Execute callback function } // To prevent rate limitations on certain Web3 RPC's we sleep here for the specified amount. tokio::time::sleep(std::time::Duration::from_millis( - gateway.invoice_delay_millis, + gateway.config.invoice_delay_millis, )) .await; } @@ -116,7 +152,7 @@ pub async fn poll_payments(gateway: PaymentGateway) { } // To prevent busy idling we sleep here too. tokio::time::sleep(std::time::Duration::from_millis( - gateway.invoice_delay_millis, + gateway.config.invoice_delay_millis, )) .await; } @@ -124,21 +160,25 @@ pub async fn poll_payments(gateway: PaymentGateway) { #[cfg(test)] mod tests { - use web3::{transports::Http, types::U256, Web3}; + + use std::str::FromStr; + + use alloy::{primitives::U256, providers::ProviderBuilder}; + use reqwest::Url; use crate::poller::get_native_balance; #[tokio::test] async fn valid_balance() { - let http = Http::new("https://bsc-dataseed1.binance.org/").unwrap(); - let web3 = Web3::new(http); + let provider = ProviderBuilder::new() + .on_http(Url::from_str("https://bsc-dataseed1.binance.org/").unwrap()); let balance = get_native_balance( - web3, + provider, "0x2170ed0880ac9a755fd29b2688956bd959f933f8".to_string(), ) .await .unwrap(); println!("Balance check: {}", balance); - assert!(balance.ge(&U256::zero())); + assert!(balance.ge(&U256::from_str("30000000000000000").unwrap())); } } diff --git a/src/transfers/errors.rs b/src/transfers/errors.rs new file mode 100644 index 0000000..19c72d5 --- /dev/null +++ b/src/transfers/errors.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum TransferError { + #[error("Could not get chain id")] + ChainId, + #[error("Could not transmit transaction")] + SendTransaction, + #[error("Could not create transaction")] + CreateTransaction, +} diff --git a/src/transfers/gas_transfers/mod.rs b/src/transfers/gas_transfers/mod.rs new file mode 100644 index 0000000..dd2d116 --- /dev/null +++ b/src/transfers/gas_transfers/mod.rs @@ -0,0 +1,99 @@ +use alloy::{ + consensus::TxEnvelope, + network::{ + eip2718::Encodable2718, Ethereum, EthereumSigner, TransactionBuilder, + TransactionBuilderError, + }, + primitives::U256, + providers::{Provider, RootProvider}, + rpc::types::eth::{TransactionReceipt, TransactionRequest}, + signers::{ + k256::ecdsa::SigningKey, + wallet::{LocalWallet, Wallet}, + }, + transports::{http::Http, RpcError, TransportErrorKind}, +}; + +use reqwest::Client; +use std::ops::Mul; + +use crate::{ + audit::log_sync, + gateway::{PaymentGateway, PaymentGatewayConfiguration}, + types::Invoice, +}; + +use super::{errors::TransferError, get_chain_id, get_gas_price}; + +/// Wrapper function for alloy's send transaction method to minimize +/// the number of nested match statements. +async fn send_transaction( + transaction: Vec, + provider: RootProvider>, +) -> Result> { + provider + .send_raw_transaction(&transaction) + .await? + .get_receipt() + .await +} + +/// Crea +async fn create_transaction( + gateway_config: PaymentGatewayConfiguration, + invoice: Invoice, + chain_id: u64, + gas_price: u128, + signer: Wallet, +) -> Result> { + let ethereum_signer: EthereumSigner = signer.into(); + + // Use specified gas limit or fallback + let gas_limit = gateway_config.transfer_gas_limit.unwrap_or(21000); + + // Maximum cost of transaction + let max_cost = gas_limit.mul(gas_price); + + // Estimated gas left after transfer + let value = invoice.amount.saturating_sub(U256::from(max_cost)); + + TransactionRequest::default() + .from(invoice.to.parse().unwrap()) + .to(gateway_config.treasury_address) + .with_nonce(0) + .with_chain_id(chain_id) + .with_gas_limit(gas_limit) + .value(value) + .with_gas_price(gas_price) + .build(ðereum_signer) + .await +} + +/// Transfers gas from a paid invoice to a specified treasury address +pub async fn transfer_gas_to_treasury( + gateway: PaymentGateway, + invoice: Invoice, +) -> Result { + let signer = LocalWallet::from_bytes(&invoice.wallet).unwrap(); + let chain_id = get_chain_id(gateway.config.provider.clone()).await?; + let gas_price = get_gas_price(gateway.config.provider.clone()).await?; + + // Create a transaction + match create_transaction(gateway.config.clone(), invoice, chain_id, gas_price, signer).await { + Ok(tx_envelope) => { + let tx_encoded = tx_envelope.encoded_2718(); + // Send transaction and await receipt + match send_transaction(tx_encoded, gateway.config.provider).await { + Ok(receipt) => Ok(receipt), + Err(error) => { + log_sync(&format!("Could not send transaction: {}", error)); + Err(TransferError::SendTransaction) + } + } + } + Err(error) => { + log_sync(&format!("Could not create transaction: {}", error)); + Err(TransferError::CreateTransaction) + } + } +} diff --git a/src/transfers/mod.rs b/src/transfers/mod.rs new file mode 100644 index 0000000..2ca6000 --- /dev/null +++ b/src/transfers/mod.rs @@ -0,0 +1,37 @@ +pub mod errors; +pub mod gas_transfers; + +use alloy::{ + providers::{Provider, RootProvider}, + transports::http::Http, +}; +use reqwest::Client; + +use crate::audit::log_sync; + +use self::errors::TransferError; + +// Retrieves the chain id from the provider. +async fn get_chain_id(provider: RootProvider>) -> Result { + match provider.get_chain_id().await { + Ok(chain_id) => Ok(chain_id), + Err(error) => { + log_sync(&format!("Could not get chain id: {}", error)); + Err(TransferError::ChainId) + } + } +} + +/// Retrieves the current gas price from a provider +async fn get_gas_price(provider: RootProvider>) -> Result { + match provider.get_gas_price().await { + Ok(gas_price) => Ok(gas_price), + Err(error) => { + log_sync(&format!( + "Could not get gas price (maybe chain uses EIP-1559?): {}", + error + )); + Err(TransferError::ChainId) + } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index 500ddea..00f6026 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,7 +1,10 @@ mod errors; use self::errors::SerializableError; +use alloy::{ + primitives::{B256, U256}, + rpc::types::eth::TransactionReceipt, +}; use serde::{Deserialize, Serialize}; -use web3::types::U256; pub trait Serializable { fn to_bin(&self) -> Result, Box>; fn from_bin(data: Vec) -> Result @@ -23,6 +26,8 @@ pub struct PaymentMethod { pub struct Invoice { /// Recipient address pub to: String, + /// Recipient instance + pub wallet: B256, /// Amount requested pub amount: U256, /// Method used for payment @@ -33,6 +38,7 @@ pub struct Invoice { pub paid_at_timestamp: u64, /// Invoice expiry time pub expires: u64, + pub receipt: Option, } impl Serializable for Invoice {