From 084304878a25452acc930d833437cad4f350086b Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 27 Jan 2020 15:34:01 -0800 Subject: [PATCH] stdtx: Initial crate Originally developed here as the `cosmos-stdtx` crate: https://github.com/tendermint/kms/pull/401 This imports the code, renames it to the more convenient `stdtx` crate, and updates links and path references to other crates in this repo. --- Cargo.lock | 540 +++++++++++++++++++++++- Cargo.toml | 1 + README.md | 3 + hkd32/src/pathbuf.rs | 2 +- stdtx/Cargo.toml | 35 ++ stdtx/README.md | 67 +++ stdtx/src/address.rs | 53 +++ stdtx/src/amino_types.rs | 118 ++++++ stdtx/src/builder.rs | 101 +++++ stdtx/src/decimal.rs | 130 ++++++ stdtx/src/error.rs | 102 +++++ stdtx/src/lib.rs | 152 +++++++ stdtx/src/msg.rs | 73 ++++ stdtx/src/msg/builder.rs | 174 ++++++++ stdtx/src/msg/field.rs | 43 ++ stdtx/src/msg/value.rs | 81 ++++ stdtx/src/schema.rs | 147 +++++++ stdtx/src/schema/definition.rs | 78 ++++ stdtx/src/schema/field.rs | 107 +++++ stdtx/src/schema/value_type.rs | 63 +++ stdtx/src/type_name.rs | 87 ++++ stdtx/tests/integration.rs | 19 + stdtx/tests/support/example_schema.toml | 32 ++ 23 files changed, 2195 insertions(+), 13 deletions(-) create mode 100644 stdtx/Cargo.toml create mode 100644 stdtx/README.md create mode 100644 stdtx/src/address.rs create mode 100644 stdtx/src/amino_types.rs create mode 100644 stdtx/src/builder.rs create mode 100644 stdtx/src/decimal.rs create mode 100644 stdtx/src/error.rs create mode 100644 stdtx/src/lib.rs create mode 100644 stdtx/src/msg.rs create mode 100644 stdtx/src/msg/builder.rs create mode 100644 stdtx/src/msg/field.rs create mode 100644 stdtx/src/msg/value.rs create mode 100644 stdtx/src/schema.rs create mode 100644 stdtx/src/schema/definition.rs create mode 100644 stdtx/src/schema/field.rs create mode 100644 stdtx/src/schema/value_type.rs create mode 100644 stdtx/src/type_name.rs create mode 100644 stdtx/tests/integration.rs create mode 100644 stdtx/tests/support/example_schema.toml diff --git a/Cargo.lock b/Cargo.lock index 584b9c24..33c181ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,11 @@ name = "autocfg" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "backtrace" version = "0.3.42" @@ -57,10 +62,15 @@ name = "backtrace-sys" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.48 (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.66 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "block-buffer" version = "0.7.3" @@ -112,7 +122,7 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.48" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -125,8 +135,16 @@ name = "chrono" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -138,6 +156,14 @@ dependencies = [ "subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "digest" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "digest" version = "0.8.1" @@ -146,6 +172,36 @@ dependencies = [ "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ecdsa" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "elliptic-curve 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "k256 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "p256 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "p384 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "signature 1.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "elliptic-curve" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "env_logger" version = "0.7.1" @@ -155,6 +211,14 @@ dependencies = [ "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "failure" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -174,6 +238,19 @@ dependencies = [ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "generic-array" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "generic-array" version = "0.12.3" @@ -243,6 +320,27 @@ dependencies = [ "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "itertools" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "k256" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "elliptic-curve 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -280,21 +378,74 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-complex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-integer" -version = "0.1.41" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-iter" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-rational" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -302,6 +453,22 @@ name = "opaque-debug" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "p256" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "elliptic-curve 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "p384" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "elliptic-curve 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pbkdf2" version = "0.3.0" @@ -321,6 +488,14 @@ name = "ppv-lite86" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "proc-macro2" version = "1.0.8" @@ -329,6 +504,29 @@ dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "prost-amino" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "prost-amino-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "quickcheck" version = "0.9.2" @@ -340,6 +538,14 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "quote" version = "1.0.2" @@ -348,6 +554,24 @@ dependencies = [ "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.7.2" @@ -360,6 +584,15 @@ dependencies = [ "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand_chacha" version = "0.2.1" @@ -369,6 +602,19 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rand_core" version = "0.5.1" @@ -377,6 +623,14 @@ dependencies = [ "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -385,6 +639,62 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "redox_syscall" version = "0.1.56" @@ -414,11 +724,27 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rust_decimal" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustc-demangle" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ryu" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "scroll" version = "0.10.1" @@ -437,6 +763,15 @@ dependencies = [ "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "secp256k1" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "secrecy" version = "0.6.0" @@ -464,6 +799,16 @@ dependencies = [ "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "serde_json" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sha2" version = "0.8.1" @@ -475,6 +820,49 @@ dependencies = [ "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "signatory" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ecdsa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "signature 1.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle-encoding 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "signatory-secp256k1" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "secp256k1 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", + "signatory 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", + "signature 1.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "signature" +version = "1.0.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "signature_derive 1.0.0-pre.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "signature_derive" +version = "1.0.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "slog" version = "2.5.2" @@ -490,11 +878,42 @@ name = "stable_deref_trait" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "stdtx" +version = "0.0.0" +dependencies = [ + "anomaly 0.1.2", + "ecdsa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "prost-amino 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "prost-amino-derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rust_decimal 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "signatory-secp256k1 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle-encoding 0.5.0", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "subtle" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "subtle" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "subtle-encoding" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "subtle-encoding" version = "0.5.0" @@ -502,6 +921,16 @@ dependencies = [ "zeroize 1.1.0", ] +[[package]] +name = "syn" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "syn" version = "1.0.14" @@ -545,6 +974,24 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thiserror" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thread_local" version = "0.3.6" @@ -553,11 +1000,24 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "typenum" version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-xid" version = "0.2.0" @@ -594,6 +1054,11 @@ dependencies = [ "zeroize_derive 1.0.0", ] +[[package]] +name = "zeroize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "zeroize_derive" version = "1.0.0" @@ -609,68 +1074,119 @@ dependencies = [ "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" "checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" "checksum backtrace 0.3.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b4b1549d804b6c73f4817df2ba073709e96e426f12987127c48e6745568c350b" "checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" +"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" "checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" +"checksum cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "8dae9c4b8fedcae85592ba623c4fd08cfdab3e3b72d6df780c6ead964a69bfff" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +"checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +"checksum ecdsa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e96b58e5e926181b2cd9bd547b672e888482644a27b5e067e7a13ee52baf5813" +"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +"checksum elliptic-curve 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "01f69be7d1feb7a7a04f158aaf32c7deaa7604e9bd58145525e536438c4e5096" "checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" "checksum findshlibs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1260d61e4fe2a6ab845ffdc426a0bd68ffb240b91cf0ec5a8d1170cec535bd8" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" "checksum gimli 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "81dd6190aad0f05ddbbf3245c54ed14ca4aa6dd32f22312b70d8f168c3e3e633" "checksum goblin 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3081214398d39e4bd7f2c1975f0488ed04614ffdd976c6fc7a0708278552c0da" "checksum hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +"checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" +"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +"checksum k256 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4407bc616f76afb6bccd8a813b86fe622752b15b43c5e60fec94d21af2393a72" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" -"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" -"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" +"checksum num 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +"checksum num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +"checksum num-complex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +"checksum num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" +"checksum num-rational 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "da4dc79f9e6c81bef96148c8f6b8e72ad4541caa4a24373e900a36da07de03a3" +"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +"checksum p256 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "812a058a5afc96a52a8ab6e250325d025254ff030ff737dc5b62576e4e604c42" +"checksum p384 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3aee0d9bdeedaea6cdd47f9281a9f8e1037d3037088b70e2af13c64ce65608ec" "checksum pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9" "checksum plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" +"checksum prost-amino 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43bcb4a37c9f20d42654f726b6ff08333a1596c25c9cbc226a4bc5b6e12ac433" +"checksum prost-amino-derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34026eba59f604c9a2b60142e1f362cd3ebc64bc0be1594627f99418f6d392b7" "checksum quickcheck 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" +"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" +"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +"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 rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +"checksum rust_decimal 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43e33d8f7b289776cbd63687e26f5f25ca4b624c8eb1c9d5ea7c2cae2097e7fb" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" "checksum scroll 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "abb2332cb595d33f7edd5700f4cbf94892e680c7f0ae56adab58a35190b66cb1" "checksum scroll_derive 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8584eea9b9ff42825b46faf46a8c24d2cff13ec152fa2a50df788b87c07ee28" +"checksum secp256k1 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4d311229f403d64002e9eed9964dfa5a0a0c1ac443344f7546bf48e916c6053a" "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +"checksum serde_json 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "eab8f15f15d6c41a154c1b128a22f2dfabe350ef53c40953d84e36155c91192b" "checksum sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" +"checksum signatory 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7acd87e0a98ac7048c5ffa8b5856e5f479f8f4e37b9edb22b1d6d8dce827a29b" +"checksum signatory-secp256k1 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d98496421006efcfa95f6a9b8822ae9d798447147c07e5d3e175fc047740c3e4" +"checksum signature 1.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a0cfcdc45066661979294e965c21b60355da35eb5d638af8143e5aa83fdfce53" +"checksum signature_derive 1.0.0-pre.0 (registry+https://github.com/rust-lang/crates.io-index)" = "30d0e333aec3605c822b1c7c760f6fa3d28a78d7d762a7b59d21fac4ebbf3680" "checksum slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1cc9c640a4adbfbcc11ffb95efe5aa7af7309e002adab54b185507dbf2377b99" "checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" +"checksum subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" +"checksum subtle-encoding 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "30492c59ec8bdeee7d6dd2d851711cae5f1361538f10ecfdcd1d377d57c2a783" +"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" "checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +"checksum thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e" +"checksum thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +"checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" diff --git a/Cargo.toml b/Cargo.toml index b67f21d9..9fc3fad6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "harp", "hkd32", "secrecy", + "stdtx", "subtle-encoding", "tai64", "zeroize", diff --git a/README.md b/README.md index 7717211e..2c39ee73 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ This repository contains the following crates: | [harp] | ![][harp-crate] | Minimalist HTTP library | | [hkd32] | ![][hkd32-crate] | HMAC-based Hierarchical Key Derivation | | [secrecy] | ![][secrecy-crate] | Simple secret-keeping library | +| [stdtx] | ![][stdtx-crate] | Cosmos StdTx builder/signer/serializer | | [subtle-encoding] | ![][subtle-encoding-crate] | Hex, Bech32, and Base64 in constant-time(ish) | | [tai64] | ![][tai64-crate] | TAI64(N) timestamp format | | [zeroize] | ![][zeroize-crate] | Securely zero memory | @@ -90,6 +91,8 @@ without any additional terms or conditions. [hkd32-crate]: https://img.shields.io/crates/v/hkd32.svg [secrecy]: https://github.com/iqlusioninc/crates/tree/develop/secrecy [secrecy-crate]: https://img.shields.io/crates/v/secrecy.svg +[stdtx]: https://github.com/iqlusioninc/crates/tree/develop/stdtx +[stdtx-crate]: https://img.shields.io/crates/v/stdtx.svg [subtle-encoding]: https://github.com/iqlusioninc/crates/tree/develop/subtle-encoding [subtle-encoding-crate]: https://img.shields.io/crates/v/subtle-encoding.svg [tai64]: https://github.com/iqlusioninc/crates/tree/develop/tai64 diff --git a/hkd32/src/pathbuf.rs b/hkd32/src/pathbuf.rs index 9dadfc24..9a24e328 100644 --- a/hkd32/src/pathbuf.rs +++ b/hkd32/src/pathbuf.rs @@ -105,7 +105,7 @@ impl FromStr for PathBuf { let mut result = Self::new(); // Special case for the root path - if s.len() == 1 && s.chars().nth(0) == Some(DELIMITER) { + if s.len() == 1 && s.starts_with(DELIMITER) { return Ok(result); } diff --git a/stdtx/Cargo.toml b/stdtx/Cargo.toml new file mode 100644 index 00000000..b45fb8cd --- /dev/null +++ b/stdtx/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "stdtx" +description = "Extensible schema-driven Cosmos StdTx builder and Amino serializer" +version = "0.0.0" # Also update html_root_url in lib.rs when bumping this +authors = ["Tony Arcieri "] +license = "Apache-2.0" +homepage = "https://github.com/iqlusioninc/crates/" +repository = "https://github.com/iqlusioninc/crates/tree/develop/stdtx" +readme = "README.md" +categories = ["cryptography", "encoding"] +keywords = ["amino", "crypto", "cosmos", "transaction", "tendermint"] +edition = "2018" + +[badges] +circle-ci = { repository = "tendermint/kms" } + +[dependencies] +anomaly = { version = "0.1", path = "../anomaly" } +ecdsa = { version = "0.4", features = ["k256"] } +prost-amino = "0.5" +prost-amino-derive = "0.5" +rust_decimal = "1.1" +serde = { version = "1", features = ["serde_derive"] } +serde_json = "1" +sha2 = "0.8" +thiserror = "1" +toml = "0.5" + +[dependencies.subtle-encoding] +version = "0.5" +features = ["bech32-preview"] +path = "../subtle-encoding" + +[dev-dependencies] +signatory-secp256k1 = "0.18" diff --git a/stdtx/README.md b/stdtx/README.md new file mode 100644 index 00000000..5c5152f0 --- /dev/null +++ b/stdtx/README.md @@ -0,0 +1,67 @@ +# stdtx.rs 🌌 iqlusion + +[![Crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +[![Safety Dance][safety-image]][safety-link] +[![Apache 2.0 Licensed][license-image]][license-link] +![MSRV][msrv-image] +[![Gitter Chat][gitter-image]][gitter-link] + +Extensible schema-driven [Cosmos] [StdTx] builder and [Amino] serializer. + +## About + +**stdtx.rs** is a Rust library for composing transactions in the [StdTx] +format used by several [Tendermint]-based networks. + +It includes support for cryptographically signing transactions and serializing +them in the [Amino] encoding format. + +Definitions of transaction types are easily extensible, and can be defined at +runtime by loading them from a TOML definition file. This allows +**stdtx.rs** to be used with any [Tendermint]-based software which +uses the [StdTx] format without requiring upstream modifications. + +## Minimum Supported Rust Version + +- Rust **1.39+** + +## License + +Copyright © 2020 Tony Arcieri + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/stdtx.svg +[crate-link]: https://crates.io/crates/stdtx +[docs-image]: https://docs.rs/stdtx/badge.svg +[docs-link]: https://docs.rs/stdtx/ +[build-image]: https://github.com/iqlusioninc/crates/workflows/Rust/badge.svg?branch=develop&event=push +[build-link]: https://github.com/iqlusioninc/crates/actions +[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg +[safety-link]: https://github.com/rust-secure-code/safety-dance/ +[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg +[license-link]: https://github.com/iqlusioninc/crates/blob/develop/LICENSE +[msrv-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg +[gitter-image]: https://badges.gitter.im/iqlusioninc/community.svg +[gitter-link]: https://gitter.im/iqlusioninc/community + +[//]: # (general links) + +[Cosmos]: https://cosmos.network/ +[StdTx]: https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth/types#StdTx +[Tendermint]: https://tendermint.com/ +[Amino]: https://github.com/tendermint/go-amino diff --git a/stdtx/src/address.rs b/stdtx/src/address.rs new file mode 100644 index 00000000..51f0a46c --- /dev/null +++ b/stdtx/src/address.rs @@ -0,0 +1,53 @@ +//! Address types (account or validator) + +use crate::error::{Error, ErrorKind}; +use anomaly::ensure; +use std::convert::TryInto; +use subtle_encoding::bech32; + +/// Size of an address +pub const ADDRESS_SIZE: usize = 20; + +/// Address type +#[derive(Clone, Debug)] +pub struct Address(pub [u8; ADDRESS_SIZE]); + +impl Address { + /// Parse an address from its Bech32 form + pub fn from_bech32(addr_bech32: impl AsRef) -> Result<(String, Address), Error> { + let (hrp, addr) = bech32::decode(addr_bech32.as_ref())?; + + ensure!( + addr.len() == ADDRESS_SIZE, + ErrorKind::Address, + "invalid length for decoded address: {} (expected {})", + addr.len(), + ADDRESS_SIZE + ); + + Ok((hrp, Address(addr.as_slice().try_into().unwrap()))) + } + + /// Encode this address as Bech32 + pub fn to_bech32(&self, hrp: &str) -> String { + bech32::encode(hrp, &self.0) + } +} + +impl AsRef<[u8]> for Address { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl From<[u8; ADDRESS_SIZE]> for Address { + fn from(addr: [u8; ADDRESS_SIZE]) -> Address { + Address(addr) + } +} + +impl From
for [u8; ADDRESS_SIZE] { + fn from(addr: Address) -> [u8; ADDRESS_SIZE] { + addr.0 + } +} diff --git a/stdtx/src/amino_types.rs b/stdtx/src/amino_types.rs new file mode 100644 index 00000000..e1681d46 --- /dev/null +++ b/stdtx/src/amino_types.rs @@ -0,0 +1,118 @@ +//! StdTx Amino types + +use crate::{Signature, TypeName}; +use prost_amino::{encode_length_delimiter, Message}; +use prost_amino_derive::Message; +use serde_json::json; + +/// StdTx Amino type +#[derive(Clone, Message)] +pub struct StdTx { + /// Messages in transction + #[prost_amino(bytes, repeated, tag = "1")] + pub msg: Vec>, + + /// Feeds to be paid + #[prost_amino(message)] + pub fee: Option, + + /// Signatures + #[prost_amino(message, repeated)] + pub signatures: Vec, + + /// Memo field + #[prost_amino(string)] + pub memo: String, +} + +impl StdTx { + /// Encode this [`StdTx`] in Amino encoding identifying it with the given + /// type name (e.g. `cosmos-sdk/StdTx`) + pub fn to_amino_bytes(&self, type_name: &TypeName) -> Vec { + let mut amino_tx = type_name.amino_prefix(); + self.encode(&mut amino_tx).expect("LEB128 encoding error"); + + let mut amino_encoded = vec![]; + encode_length_delimiter(amino_tx.len(), &mut amino_encoded).expect("LEB128 encoding error"); + amino_encoded.append(&mut amino_tx); + amino_encoded + } +} + +/// StdFee amino type +#[derive(Clone, Message)] +pub struct StdFee { + /// Fee to be paid + #[prost_amino(message, repeated, tag = "1")] + pub amount: Vec, + + /// Gas requested for transaction + #[prost_amino(uint64)] + pub gas: u64, +} + +impl StdFee { + /// Create a [`StdFee`] for a gas-only transaction + pub fn for_gas(gas: u64) -> Self { + StdFee { + amount: vec![], + gas, + } + } + /// Compute `serde_json::Value` representing this fee + pub fn to_json_value(&self) -> serde_json::Value { + let amount = self + .amount + .iter() + .map(|amt| amt.to_json_value()) + .collect::>(); + + json!({ + "amount": amount, + "gas": self.gas.to_string() + }) + } +} + +/// Coin Amino type +#[derive(Clone, Message)] +pub struct Coin { + /// Denomination of coin + #[prost_amino(string, tag = "1")] + pub denom: String, + + /// Amount of the given denomination + #[prost_amino(string)] + pub amount: String, +} + +impl Coin { + /// Compute `serde_json::Value` representing this coin + pub fn to_json_value(&self) -> serde_json::Value { + json!({ + "denom": self.denom, + "amount": self.amount + }) + } +} + +/// StdSignature amino type +#[derive(Clone, Message)] +pub struct StdSignature { + /// Public key which can verify this signature + #[prost_amino(bytes, tag = "1", amino_name = "tendermint/PubKeySecp256k1")] + pub pub_key: Vec, + + /// Serialized signature + #[prost_amino(bytes)] + pub signature: Vec, +} + +impl From for StdSignature { + fn from(signature: Signature) -> StdSignature { + StdSignature { + pub_key: vec![], + signature: signature.as_ref().to_vec(), + } + } +} diff --git a/stdtx/src/builder.rs b/stdtx/src/builder.rs new file mode 100644 index 00000000..56a60721 --- /dev/null +++ b/stdtx/src/builder.rs @@ -0,0 +1,101 @@ +//! Builder for `StdTx` transactions which handles construction and signing. + +use crate::{Error, Msg, Schema, Signer, StdFee, StdSignature, StdTx}; +use serde_json::json; + +/// [`StdTx`] transaction builder, which handles construction, signing, and +/// Amino serialization. +pub struct Builder { + /// Schema which describes valid transaction types + schema: Schema, + + /// Account number to include in transactions + account_number: u64, + + /// Chain ID + chain_id: String, + + /// Transaction signer + signer: Box, +} + +impl Builder { + /// Create a new transaction builder + pub fn new( + schema: Schema, + account_number: u64, + chain_id: impl Into, + signer: Box, + ) -> Self { + Self { + schema, + account_number, + chain_id: chain_id.into(), + signer, + } + } + + /// Borrow this transaction builder's [`Schema`] + pub fn schema(&self) -> &Schema { + &self.schema + } + + /// Get this transaction builder's account number + pub fn account_number(&self) -> u64 { + self.account_number + } + + /// Borrow this transaction builder's chain ID + pub fn chain_id(&self) -> &str { + &self.chain_id + } + + /// Build and sign a transaction containing the given messages + pub fn sign_tx( + &self, + sequence: u64, + fee: StdFee, + memo: &str, + messages: &[Msg], + ) -> Result { + let sign_msg = self.create_sign_msg(sequence, &fee, memo, messages); + let signature = StdSignature::from(self.signer.try_sign(sign_msg.as_bytes())?); + + Ok(StdTx { + msg: messages.iter().map(|msg| msg.to_amino_bytes()).collect(), + fee: Some(fee), + signatures: vec![signature], + memo: memo.to_owned(), + }) + } + + /// Build, sign, and encode a transaction in Amino format + pub fn sign_amino_tx( + &self, + sequence: u64, + fee: StdFee, + memo: &str, + messages: &[Msg], + ) -> Result, Error> { + let tx = self.sign_tx(sequence, fee, memo, messages)?; + Ok(tx.to_amino_bytes(self.schema.namespace())) + } + + /// Create the JSON message to sign for this transaction + fn create_sign_msg(&self, sequence: u64, fee: &StdFee, memo: &str, messages: &[Msg]) -> String { + let messages = messages + .iter() + .map(|msg| msg.to_json_value(&self.schema)) + .collect::>(); + + json!({ + "account_number": self.account_number.to_string(), + "chain_id": self.chain_id, + "fee": fee.to_json_value(), + "memo": memo, + "msgs": messages, + "sequence": sequence.to_string() + }) + .to_string() + } +} diff --git a/stdtx/src/decimal.rs b/stdtx/src/decimal.rs new file mode 100644 index 00000000..b5a4c1f1 --- /dev/null +++ b/stdtx/src/decimal.rs @@ -0,0 +1,130 @@ +//! Decimal type providing equivalent semantics to Cosmos [`sdk.Dec`] +//! +//! [`sdk.Dec`]: https://godoc.org/github.com/cosmos/cosmos-sdk/types#Dec + +use crate::error::{Error, ErrorKind}; +use anomaly::{ensure, fail}; +use std::{ + convert::{TryFrom, TryInto}, + fmt::{self, Debug, Display}, + str::FromStr, +}; + +/// Number of decimal places used by `sdk.Dec` +/// See: +pub const PRECISION: u32 = 18; + +/// Maximum value of the decimal part of an `sdk.Dec` +pub const FRACTIONAL_DIGITS_MAX: u64 = 9_999_999_999_999_999_999; + +/// Decimal type which follows Cosmos [`sdk.Dec`] conventions. +/// +/// [`sdk.Dec`]: https://godoc.org/github.com/cosmos/cosmos-sdk/types#Dec +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] +pub struct Decimal(rust_decimal::Decimal); + +impl Decimal { + /// Create a new [`Decimal`] with the given whole number and decimal + /// parts. The decimal part assumes 18 digits of precision e.g. a + /// decimal with `(1, 1)` is `1.000000000000000001`. + /// + /// 18 digits required by the Cosmos SDK. See: + /// See: + pub fn new(integral_digits: i64, fractional_digits: u64) -> Result { + ensure!( + fractional_digits <= FRACTIONAL_DIGITS_MAX, + ErrorKind::Decimal, + "fractional digits exceed available precision: {}", + fractional_digits + ); + + let integral_digits: rust_decimal::Decimal = integral_digits.into(); + let fractional_digits: rust_decimal::Decimal = fractional_digits.into(); + let precision_exp: rust_decimal::Decimal = 10u64.pow(PRECISION).into(); + + let mut combined_decimal = (integral_digits * precision_exp) + fractional_digits; + combined_decimal.set_scale(PRECISION)?; + Ok(Decimal(combined_decimal)) + } + + /// Serialize this [`Decimal`] as Amino-encoded bytes + pub fn to_amino_bytes(mut self) -> Vec { + self.0 + .set_scale(0) + .expect("can't rescale decimal for Amino serialization"); + self.to_string().into_bytes() + } +} + +impl Debug for Decimal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl Display for Decimal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl FromStr for Decimal { + type Err = Error; + + fn from_str(s: &str) -> Result { + s.parse::()?.try_into() + } +} + +impl TryFrom for Decimal { + type Error = Error; + + fn try_from(mut decimal_value: rust_decimal::Decimal) -> Result { + match decimal_value.scale() { + 0 => { + let exp: rust_decimal::Decimal = 10u64.pow(PRECISION).into(); + decimal_value *= exp; + decimal_value.set_scale(PRECISION)?; + } + PRECISION => (), + other => fail!( + ErrorKind::Decimal, + "invalid decimal precision: {} (must be 0 or 18)", + other + ), + } + + Ok(Decimal(decimal_value)) + } +} + +macro_rules! impl_from_primitive_int_for_decimal { + ($($int:ty),+) => { + $(impl From<$int> for Decimal { + fn from(num: $int) -> Decimal { + Decimal::new(num as i64, 0).unwrap() + } + })+ + }; +} + +impl_from_primitive_int_for_decimal!(i8, i16, i32, i64, isize); +impl_from_primitive_int_for_decimal!(u8, u16, u32, u64, usize); + +#[cfg(test)] +mod tests { + use super::Decimal; + + /// Used by e.g. JSON + #[test] + fn string_serialization_test() { + let num = Decimal::from(-1i8); + assert_eq!(num.to_string(), "-1.000000000000000000") + } + + #[test] + fn amino_serialization_test() { + let num = Decimal::from(-1i8); + assert_eq!(b"-1000000000000000000", num.to_amino_bytes().as_slice()); + } +} diff --git a/stdtx/src/error.rs b/stdtx/src/error.rs new file mode 100644 index 00000000..a9c8dd30 --- /dev/null +++ b/stdtx/src/error.rs @@ -0,0 +1,102 @@ +//! Error types + +use anomaly::{BoxError, Context}; +use std::{ + fmt::{self, Display}, + ops::Deref, +}; +use thiserror::Error; + +/// Kinds of errors +#[derive(Copy, Clone, Debug, Error, Eq, PartialEq)] +pub enum ErrorKind { + /// Malformed account or validator address + #[error("address error")] + Address, + + /// Invalid decimal value + #[error("invalid decimal value")] + Decimal, + + /// Input/output errors + #[error("I/O error")] + Io, + + /// Parse error + #[error("parse error")] + Parse, + + /// Signature error + #[error("signature error")] + Signature, + + /// Invalid type + #[error("type error")] + Type, +} + +impl ErrorKind { + /// Add context to an [`ErrorKind`] + pub fn context(self, source: impl Into) -> Context { + Context::new(self, Some(source.into())) + } +} + +/// Error type +#[derive(Debug)] +pub struct Error(Box>); + +impl Deref for Error { + type Target = Context; + + fn deref(&self) -> &Context { + &self.0 + } +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.0.source() + } +} + +impl From for Error { + fn from(kind: ErrorKind) -> Self { + Context::new(kind, None).into() + } +} + +impl From> for Error { + fn from(context: Context) -> Self { + Error(Box::new(context)) + } +} + +impl From for Error { + fn from(err: rust_decimal::Error) -> Error { + Context::new(ErrorKind::Decimal, Some(err.into())).into() + } +} + +impl From for Error { + fn from(err: ecdsa::signature::Error) -> Error { + Context::new(ErrorKind::Signature, Some(err.into())).into() + } +} +impl From for Error { + fn from(err: subtle_encoding::Error) -> Error { + Context::new(ErrorKind::Parse, Some(err.into())).into() + } +} + +impl From for Error { + fn from(err: toml::de::Error) -> Error { + Context::new(ErrorKind::Parse, Some(err.into())).into() + } +} diff --git a/stdtx/src/lib.rs b/stdtx/src/lib.rs new file mode 100644 index 00000000..12bcc227 --- /dev/null +++ b/stdtx/src/lib.rs @@ -0,0 +1,152 @@ +//! Extensible schema-driven builder, signer, and Amino serializer for +//! Cosmos SDK-formatted `StdTx` transactions, the standard transaction format +//! used by the Cosmos SDK and other Tendermint blockchains which use types +//! from the Cosmos SDK. +//! +//! Uses a TOML-based schema description language for `sdk.Msg` values which +//! should be encoded into the final `StdTx`. +//! +//! Includes a `StdTx` builder capable of constructing `sdk.Msg` values and +//! signing them using any ECDSA secp256k1 signer compatible with the +//! [`ecdsa` crate] (e.g. [`signatory-secp256k1`], [`yubihsm`]). +//! +//! # Equivalent Go code +//! +//! - [`StdTx` (godoc)](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth/types#StdTx) +//! - [`sdk.Msg` (godoc)](httpshttps://docs.rs/ecdsa://godoc.org/github.com/cosmos/cosmos-sdk/types#Msg) +//! +//! # Usage +//! +//! Below is a self-contained example of how to use [`stdtx::Builder`] +//! type to construct a signed [`StdTx`] message: +//! +//! ``` +//! use stdtx::Builder; +//! use signatory_secp256k1::{SecretKey, EcdsaSigner}; +//! +//! /// Example account number +//! const ACCOUNT_NUMBER: u64 = 946827; +//! +//! /// Example chain ID +//! const CHAIN_ID: &str = "columbus-3"; +//! +//! /// Example oracle feeder for `oracle/MsgExchangeRateVote` +//! const FEEDER: &str = "terra1t9et8wjeh8d0ewf4lldchterxsmhpcgg5auy47"; +//! +//! /// Example oracle validator for `oracle/MsgExchangeRateVote` +//! const VALIDATOR: &str = "terravaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03x2mfyu7"; +//! +//! /// Example amount of gas to include in transaction +//! const GAS_AMOUNT: u64 = 200000; +//! +//! /// Example StdTx message schema definition. See docs for the +//! /// `stdtx::Schema` type for more information: +//! /// +//! /// +//! /// Message types taken from Terra's oracle voter transactions: +//! /// +//! pub const TERRA_SCHEMA: &str = r#" +//! namespace = "core/StdTx" +//! acc_prefix = "terra" +//! val_prefix = "terravaloper" +//! +//! [[definition]] +//! type_name = "oracle/MsgExchangeRatePrevote" +//! fields = [ +//! { name = "hash", type = "string" }, +//! { name = "denom", type = "string" }, +//! { name = "feeder", type = "sdk.AccAddress" }, +//! { name = "validator", type = "sdk.ValAddress" }, +//! ] +//! +//! [[definition]] +//! type_name = "oracle/MsgExchangeRateVote" +//! fields = [ +//! { name = "exchange_rate", type = "sdk.Dec"}, +//! { name = "salt", type = "string" }, +//! { name = "denom", type = "string" }, +//! { name = "feeder", type = "sdk.AccAddress" }, +//! { name = "validator", type = "sdk.ValAddress" }, +//! ] +//! "#; +//! +//! /// Simple error type +//! #[derive(Debug)] +//! struct Error(String); +//! +//! impl From for Error { +//! fn from(err: stdtx::Error) -> Error { +//! Error(err.to_string()) +//! } +//! } +//! +//! /// Simple builder for an `oracle/MsgExchangeRateVote` message +//! fn build_vote_msg(schema: &stdtx::Schema) -> Result { +//! Ok(stdtx::msg::Builder::new(schema, "oracle/MsgExchangeRateVote")? +//! .decimal("exchange_rate", -1i8)? +//! .string("salt", "XXXX")? +//! .string("denom", "ukrw")? +//! .acc_address_bech32("feeder", FEEDER)? +//! .val_address_bech32("validator", VALIDATOR)? +//! .to_msg()) +//! } +//! +//! /// Parse the TOML schema for Terra `sdk.Msg` types +//! let schema = TERRA_SCHEMA.parse::().unwrap(); +//! +//! /// Create ECDSA signer (ordinarily you wouldn't generate a random key +//! /// every time but reuse an existing one) +//! let signer = EcdsaSigner::from(&SecretKey::generate()); +//! +//! /// Create message builder, giving it an account number, chain ID, and a +//! /// boxed ECDSA secp256k1 signer +//! let builder = stdtx::Builder::new(schema, ACCOUNT_NUMBER, CHAIN_ID, Box::new(signer)); +//! +//! /// Create message to be included in the `StdTx` using the method defined above +//! let msg = build_vote_msg(builder.schema()).unwrap(); +//! +//! /// Build transaction, returning serialized Amino bytes as a `Vec` +//! let sequence_number = 123456; +//! let fee = stdtx::StdFee::for_gas(GAS_AMOUNT); +//! let memo = ""; +//! let amino_bytes = builder +//! .sign_amino_tx(sequence_number, fee, memo, &[msg]) +//! .unwrap(); +//! +//! // `amino_bytes` is now a `Vec` containing an Amino serialized transaction +//! ``` +//! +//! [`ecdsa` crate]: https://docs.rs/ecdsa +//! [`signatory-secp256k1`]: https://docs.rs/signatory-secp256k1 +//! [`yubihsm`]: https://docs.rs/yubihsm +//! [`stdtx::Builder`]: https://docs.rs/stdtx/latest/stdtx/stdtx/struct.Builder.html + +#![doc(html_root_url = "https://docs.rs/stdtx/0.0.0")] +#![forbid(unsafe_code)] +#![warn(rust_2018_idioms, missing_docs, unused_qualifications)] + +pub mod address; +pub mod amino_types; +pub mod builder; +pub mod decimal; +pub mod error; +pub mod msg; +pub mod schema; +pub mod type_name; + +pub use self::{ + address::Address, + amino_types::{StdFee, StdSignature, StdTx}, + builder::Builder, + decimal::Decimal, + error::Error, + msg::Msg, + schema::Schema, + type_name::TypeName, +}; + +/// Fixed-width ECDSA secp256k1 signature +pub use ecdsa::curve::secp256k1::FixedSignature as Signature; + +/// Transaction signer for ECDSA secp256k1 signatures +pub type Signer = dyn ecdsa::signature::Signer; diff --git a/stdtx/src/msg.rs b/stdtx/src/msg.rs new file mode 100644 index 00000000..2f729b8b --- /dev/null +++ b/stdtx/src/msg.rs @@ -0,0 +1,73 @@ +//! Transaction message type i.e [`sdk.Msg`] +//! +//! [`sdk.Msg`]: https://godoc.org/github.com/cosmos/cosmos-sdk/types#Msg + +mod builder; +mod field; +mod value; + +pub use self::{builder::Builder, field::Field, value::Value}; + +use crate::{Schema, TypeName}; +use prost_amino::encode_length_delimiter as encode_leb128; // Little-endian Base 128 +use std::{collections::BTreeMap, iter::FromIterator}; + +/// Tags are indexes which identify message fields +pub type Tag = u64; + +/// Transaction message type i.e. [`sdk.Msg`]. +/// These serve as the payload for [`StdTx`] transactions. +/// +/// [`StdTx`]: https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth/types#StdTx +/// [`sdk.Msg`]: https://godoc.org/github.com/cosmos/cosmos-sdk/types#Msg +#[derive(Clone, Debug)] +pub struct Msg { + /// Name of the message type + type_name: TypeName, + + /// Fields in the message + fields: Vec, +} + +impl Msg { + /// Compute `serde_json::Value` representing a `sdk.Msg` + pub fn to_json_value(&self, schema: &Schema) -> serde_json::Value { + // `BTreeMap` ensures fields are ordered for Cosmos's Canonical JSON + let mut values = BTreeMap::new(); + + for field in &self.fields { + values.insert( + field.name().to_string(), + field.value().to_json_value(schema), + ); + } + + let mut json = serde_json::Map::new(); + json.insert( + "type".to_owned(), + serde_json::Value::String(self.type_name.to_string()), + ); + json.insert( + "value".to_owned(), + serde_json::Map::from_iter(values.into_iter()).into(), + ); + serde_json::Value::Object(json) + } + + /// Encode this message in the Amino wire format + pub fn to_amino_bytes(&self) -> Vec { + let mut result = self.type_name.amino_prefix(); + + for field in &self.fields { + // Compute the field prefix, which encodes the tag and wire type code + let prefix = field.tag() << 3 | field.value().wire_type(); + encode_leb128(prefix as usize, &mut result).expect("LEB128 encoding error"); + + let mut encoded_value = field.value().to_amino_bytes(); + encode_leb128(encoded_value.len(), &mut result).expect("LEB128 encoding error"); + result.append(&mut encoded_value); + } + + result + } +} diff --git a/stdtx/src/msg/builder.rs b/stdtx/src/msg/builder.rs new file mode 100644 index 00000000..43e262af --- /dev/null +++ b/stdtx/src/msg/builder.rs @@ -0,0 +1,174 @@ +//! Transaction message builder + +use super::{Field, Msg, Value}; +use crate::{ + address::Address, + decimal::Decimal, + error::{Error, ErrorKind}, + schema::{Definition, Schema, ValueType}, + type_name::TypeName, +}; +use anomaly::{ensure, format_err}; +use std::convert::TryInto; + +/// Transaction message builder +pub struct Builder<'a> { + /// Schema for the message we're building + schema_definition: &'a Definition, + + /// Name of the message type + type_name: TypeName, + + /// Bech32 prefix for account addresses + acc_prefix: String, + + /// Bech32 prefix for validator consensus addresses + val_prefix: String, + + /// Fields in the message + fields: Vec, +} + +impl<'a> Builder<'a> { + /// Create a new message builder for the given schema and message type + pub fn new( + schema: &'a Schema, + type_name: impl TryInto, + ) -> Result { + let type_name = type_name.try_into()?; + + let schema_definition = schema.get_definition(&type_name).ok_or_else(|| { + format_err!( + ErrorKind::Type, + "type not found in schema: `{}`", + &type_name + ) + })?; + + Ok(Self { + schema_definition, + type_name, + acc_prefix: schema.acc_prefix().to_owned(), + val_prefix: schema.val_prefix().to_owned(), + fields: vec![], + }) + } + + /// `sdk.AccAddress`: Cosmos SDK account addresses + /// + pub fn acc_address( + &mut self, + field_name: impl TryInto, + address: Address, + ) -> Result<&mut Self, Error> { + let field_name = field_name.try_into()?; + let tag = self + .schema_definition + .get_field_tag(&field_name, ValueType::SdkAccAddress)?; + + let field = Field::new(tag, field_name, Value::SdkAccAddress(address)); + + self.fields.push(field); + Ok(self) + } + + /// `sdk.AccAddress` encoded as Bech32 + pub fn acc_address_bech32( + &mut self, + field_name: impl TryInto, + addr_bech32: impl AsRef, + ) -> Result<&mut Self, Error> { + let (hrp, address) = Address::from_bech32(addr_bech32)?; + + ensure!( + hrp == self.acc_prefix, + ErrorKind::Address, + "invalid account address prefix: `{}` (expected `{}`)", + hrp, + self.acc_prefix, + ); + + self.acc_address(field_name, address) + } + + /// `sdk.Dec`: Cosmos SDK decimals + /// s + pub fn decimal( + &mut self, + field_name: impl TryInto, + value: impl Into, + ) -> Result<&mut Self, Error> { + let field_name = field_name.try_into()?; + + let tag = self + .schema_definition + .get_field_tag(&field_name, ValueType::SdkDecimal)?; + + let field = Field::new(tag, field_name, Value::SdkDecimal(value.into())); + + self.fields.push(field); + Ok(self) + } + + /// `sdk.ValAddress`: Cosmos SDK validator addresses + /// + pub fn val_address( + &mut self, + field_name: impl TryInto, + address: Address, + ) -> Result<&mut Self, Error> { + let field_name = field_name.try_into()?; + let tag = self + .schema_definition + .get_field_tag(&field_name, ValueType::SdkValAddress)?; + + let field = Field::new(tag, field_name, Value::SdkValAddress(address)); + + self.fields.push(field); + Ok(self) + } + + /// `sdk.ValAddress` encoded as Bech32 + pub fn val_address_bech32( + &mut self, + field_name: impl TryInto, + addr_bech32: impl AsRef, + ) -> Result<&mut Self, Error> { + let (hrp, address) = Address::from_bech32(addr_bech32)?; + + ensure!( + hrp == self.val_prefix, + ErrorKind::Address, + "invalid validator address prefix: `{}` (expected `{}`)", + hrp, + self.val_prefix, + ); + + self.val_address(field_name, address) + } + + /// Strings + pub fn string( + &mut self, + field_name: impl TryInto, + s: impl Into, + ) -> Result<&mut Self, Error> { + let field_name = field_name.try_into()?; + let tag = self + .schema_definition + .get_field_tag(&field_name, ValueType::String)?; + + let field = Field::new(tag, field_name, Value::String(s.into())); + + self.fields.push(field); + Ok(self) + } + + /// Consume this builder and output a message + pub fn to_msg(&self) -> Msg { + Msg { + type_name: self.type_name.clone(), + fields: self.fields.clone(), + } + } +} diff --git a/stdtx/src/msg/field.rs b/stdtx/src/msg/field.rs new file mode 100644 index 00000000..8494056c --- /dev/null +++ b/stdtx/src/msg/field.rs @@ -0,0 +1,43 @@ +//! Message fields + +use super::{Tag, Value}; +use crate::type_name::TypeName; + +/// Message fields +#[derive(Clone, Debug)] +pub struct Field { + /// Field number to use as the key in an Amino message. + tag: Tag, + + /// Name of this field + name: TypeName, + + /// Amino type to serialize this field as + value: Value, +} + +impl Field { + /// Create a new message field + pub fn new(tag: Tag, name: TypeName, value: impl Into) -> Self { + Self { + tag, + name, + value: value.into(), + } + } + + /// Get this field's [`Tag`] + pub fn tag(&self) -> Tag { + self.tag + } + + /// Get this field's [`TypeName`] + pub fn name(&self) -> &TypeName { + &self.name + } + + /// Get this field's [`Value`] + pub fn value(&self) -> &Value { + &self.value + } +} diff --git a/stdtx/src/msg/value.rs b/stdtx/src/msg/value.rs new file mode 100644 index 00000000..73e346ed --- /dev/null +++ b/stdtx/src/msg/value.rs @@ -0,0 +1,81 @@ +//! Message values + +use crate::{ + address::Address, + decimal::Decimal, + schema::{Schema, ValueType}, +}; + +/// Message values - data contained in fields of a message +#[derive(Clone, Debug)] +pub enum Value { + /// `sdk.AccAddress`: Cosmos SDK account addresses + /// + SdkAccAddress(Address), + + /// `sdk.Dec`: Cosmos SDK decimals + /// + SdkDecimal(Decimal), + + /// `sdk.ValAddress`: Cosmos SDK validator addresses + /// + SdkValAddress(Address), + + /// Strings + String(String), +} + +impl Value { + /// Get the type of this value + pub fn value_type(&self) -> ValueType { + match self { + Value::SdkAccAddress(_) => ValueType::SdkAccAddress, + Value::SdkDecimal(_) => ValueType::SdkDecimal, + Value::SdkValAddress(_) => ValueType::SdkValAddress, + Value::String(_) => ValueType::String, + } + } + + /// Get the Amino/Proto wire type for this field + /// See: + pub(super) fn wire_type(&self) -> u64 { + match self { + // Length-delimited types + Value::SdkAccAddress(_) + | Value::SdkDecimal(_) + | Value::SdkValAddress(_) + | Value::String(_) => 2, + } + } + + /// Encode this value as Amino bytes + pub(super) fn to_amino_bytes(&self) -> Vec { + match self { + Value::SdkAccAddress(addr) | Value::SdkValAddress(addr) => addr.as_ref().to_vec(), + Value::SdkDecimal(decimal) => decimal.to_amino_bytes(), + Value::String(s) => s.clone().into_bytes(), + } + } + + /// Encode this value as a [`serde_json::Value`] + pub(super) fn to_json_value(&self, schema: &Schema) -> serde_json::Value { + serde_json::Value::String(match self { + Value::SdkAccAddress(addr) => addr.to_bech32(schema.acc_prefix()), + Value::SdkDecimal(decimal) => decimal.to_string(), + Value::SdkValAddress(addr) => addr.to_bech32(schema.val_prefix()), + Value::String(s) => s.clone(), + }) + } +} + +impl From for Value { + fn from(dec: Decimal) -> Value { + Value::SdkDecimal(dec) + } +} + +impl From for Value { + fn from(s: String) -> Value { + Value::String(s) + } +} diff --git a/stdtx/src/schema.rs b/stdtx/src/schema.rs new file mode 100644 index 00000000..95674d92 --- /dev/null +++ b/stdtx/src/schema.rs @@ -0,0 +1,147 @@ +//! Amino schema for an [`sdk.Msg`]. +//! +//! Schema files are similar to Protobuf schemas, but use a TOML-based syntax. +//! +//! # Example TOML File +//! +//! Below is an example TOML file defining an `sdk.Msg`. This example defines +//! a type named `oracle/MsgExchangeRatePrevote`: +//! +//! ```toml +//! # Example StdTx message schema definition. +//! # +//! # Message types taken from Terra's oracle voter transactions: +//! # +//! +//! # StdTx namespace for schema definitions +//! # (e.g. `cosmos-sdk/StdTx` for Cosmos SDK) +//! namespace = "core/StdTx" +//! +//! # Bech32 address prefixes +//! acc_prefix = "terra" +//! val_prefix = "terravaloper" +//! +//! [[definition]] +//! type_name = "oracle/MsgExchangeRatePrevote" +//! fields = [ +//! { name = "hash", type = "string" }, +//! { name = "denom", type = "string" }, +//! { name = "feeder", type = "sdk.AccAddress" }, +//! { name = "validator", type = "sdk.ValAddress" }, +//! ] +//! +//! [[definition]] +//! type_name = "oracle/MsgExchangeRateVote" +//! fields = [ +//! # explicit field tag example - will start from "1" otherwise +//! { name = "exchange_rate", type = "sdk.Dec", tag = 1 }, +//! { name = "salt", type = "string" }, +//! { name = "denom", type = "string" }, +//! { name = "feeder", type = "sdk.AccAddress" }, +//! { name = "validator", type = "sdk.ValAddress" }, +//! ] +//! ``` +//! +//! [`sdk.Msg`]: https://godoc.org/github.com/cosmos/cosmos-sdk/types#Msg + +mod definition; +mod field; +mod value_type; + +pub use self::{definition::Definition, field::Field, value_type::ValueType}; + +use crate::{ + error::{Error, ErrorKind}, + type_name::TypeName, +}; +use anomaly::fail; +use serde::Deserialize; +use std::{fs, path::Path, str::FromStr}; + +/// Schema definition for an [`sdk.Msg`] to be included in an [`StdTx`]. +/// +/// The schema includes information about field identifiers and associated types. +/// +/// [`StdTx`]: https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth/types#StdTx +/// [`sdk.Msg`]: https://godoc.org/github.com/cosmos/cosmos-sdk/types#Msg +#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[serde(deny_unknown_fields)] +pub struct Schema { + /// `StdTx` namespace for schema (e.g. `cosmos-sdk/StdTx`) + namespace: TypeName, + + /// Bech32 prefix for account addresses + acc_prefix: String, + + /// Bech32 prefix for validator consensus addresses + val_prefix: String, + + /// Schema definitions + #[serde(rename = "definition")] + definitions: Vec, +} + +impl Schema { + /// Create a new [`Schema`] with the given `StdTx` namespace and [`Definition`] set + pub fn new( + namespace: TypeName, + acc_prefix: impl Into, + val_prefix: impl Into, + definitions: impl Into>, + ) -> Self { + Self { + namespace, + acc_prefix: acc_prefix.into(), + val_prefix: val_prefix.into(), + definitions: definitions.into(), + } + } + + /// Load a TOML file describing + pub fn load_toml(path: impl AsRef) -> Result { + match fs::read_to_string(path.as_ref()) { + Ok(s) => s.parse(), + Err(e) => fail!( + ErrorKind::Io, + "couldn't open {}: {}", + path.as_ref().display(), + e + ), + } + } + + /// Get the transaction namespace for this schema (e.g. `cosmos-sdk/StdTx`) + pub fn namespace(&self) -> &TypeName { + &self.namespace + } + + /// Get the Bech32 prefix for account addresses + pub fn acc_prefix(&self) -> &str { + self.acc_prefix.as_ref() + } + + /// Get the Bech32 prefix for validator addresses + pub fn val_prefix(&self) -> &str { + self.val_prefix.as_ref() + } + + /// [`Definition`] types found in this [`Schema`] + pub fn definitions(&self) -> &[Definition] { + &self.definitions + } + + /// Get a schema [`Definition`] for the given [`TypeName`] + pub fn get_definition(&self, type_name: &TypeName) -> Option<&Definition> { + self.definitions + .iter() + .find(|def| def.type_name() == type_name) + } +} + +impl FromStr for Schema { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(toml::from_str(s)?) + } +} diff --git a/stdtx/src/schema/definition.rs b/stdtx/src/schema/definition.rs new file mode 100644 index 00000000..9de3ee27 --- /dev/null +++ b/stdtx/src/schema/definition.rs @@ -0,0 +1,78 @@ +//! Type definition within a schema + +use super::{field, Field, TypeName, ValueType}; +use crate::{ + error::{Error, ErrorKind}, + msg::Tag, +}; +use anomaly::{fail, format_err}; +use serde::Deserialize; + +/// Definition of a particular type in the schema +#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[serde(deny_unknown_fields)] +pub struct Definition { + /// Name of the type this definition is for + type_name: TypeName, + + /// Fields in this type definition + #[serde(deserialize_with = "field::deserialize_vec")] + fields: Vec, +} + +impl Definition { + /// Create a new schema [`Definition`] with the given type name and fields + pub fn new(type_name: TypeName, fields: impl Into>) -> Result { + let fields = fields.into(); + + if let Err(e) = field::validate(&fields) { + fail!(ErrorKind::Parse, "{}", e); + } + + Ok(Self { type_name, fields }) + } + + /// Get the [`TypeName`] defined by this schema. + pub fn type_name(&self) -> &TypeName { + &self.type_name + } + + /// Get a list of [`Field`] types in this schema. + pub fn fields(&self) -> &[Field] { + self.fields.as_slice() + } + + /// Get a [`Field`] by its [`TypeName`] + pub fn get_field(&self, field_name: &TypeName) -> Option<&Field> { + self.fields.iter().find(|field| field.name() == field_name) + } + + /// Get the [`Tag`] for a [`Field`], ensuring is of the given [`ValueType`] + pub fn get_field_tag( + &self, + field_name: &TypeName, + value_type: ValueType, + ) -> Result { + let field = self.get_field(field_name).ok_or_else(|| { + format_err!( + ErrorKind::Type, + "field name not found in `{}` schema: `{}`", + &self.type_name, + field_name + ) + })?; + + if field.value_type() != value_type { + fail!( + ErrorKind::Type, + "field `{}` of `{}` is not an {} (expected {})", + field_name, + &self.type_name, + value_type, + field.value_type() + ); + } + + Ok(field.tag()) + } +} diff --git a/stdtx/src/schema/field.rs b/stdtx/src/schema/field.rs new file mode 100644 index 00000000..8c066c61 --- /dev/null +++ b/stdtx/src/schema/field.rs @@ -0,0 +1,107 @@ +//! Fields in a type definition + +use super::ValueType; +use crate::{msg::Tag, type_name::TypeName}; +use serde::{de, Deserialize}; +use std::collections::BTreeSet as Set; + +/// Fields in an Amino-serialized `sdk.Msg` +#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[serde(deny_unknown_fields)] +pub struct Field { + /// Name of this field + name: TypeName, + + /// Amino type to serialize this field as + #[serde(rename = "type")] + value_type: ValueType, + + /// Field number to use as the key in an Amino message. + /// + /// These are all ensured to be `Some` in the `deserialize_vec` method below. + tag: Option, +} + +impl Field { + /// Create a new [`Field`] with the given tag and [`ValueType`]. + pub fn new(name: TypeName, value_type: ValueType, tag: Tag) -> Self { + Self { + name, + tag: Some(tag), + value_type, + } + } + + /// Get the [`TypeName`] for this [`Field`] + pub fn name(&self) -> &TypeName { + &self.name + } + + /// Get the [`ValueType`] for this [`Field`] + pub fn value_type(&self) -> ValueType { + self.value_type + } + + /// Get the numerical index [`Tag`] for this [`Field`] + pub fn tag(&self) -> Tag { + self.tag.unwrap() + } +} + +/// Deserialize `Vec`, populating their `tag` if unpopulated +pub(super) fn deserialize_vec<'de, D>(deserializer: D) -> Result, D::Error> +where + D: de::Deserializer<'de>, +{ + let mut fields: Vec = Vec::deserialize(deserializer)?; + populate_tags(&mut fields).map_err(de::Error::custom)?; + validate(&fields).map_err(de::Error::custom)?; + Ok(fields) +} + +/// Populate the `tag` for [`Field`] values if unset +fn populate_tags(fields: &mut [Field]) -> Result<(), &str> { + // Tags are 1-indexed + let mut tag = 1; + + for field in fields { + match field.tag { + Some(t) => { + if t == 0 { + // `0` is not allowed as a field tag + return Err("invalid field tag: 0"); + } + + // auto index by last specified tag + tag = t + 1 + } + None => { + field.tag = Some(tag); + tag += 1; + } + } + } + + Ok(()) +} + +/// Ensure field names and tags are unique across all fields +pub(super) fn validate(fields: &[Field]) -> Result<(), String> { + let mut names = Set::new(); + let mut tags = Set::new(); + + for field in fields { + // This invariant is enforced in `populate_tags` and the `Field::new` methods + let tag = field.tag.expect("field with unpopulated tag!"); + + if !names.insert(&field.name) { + return Err(format!("duplicate field name: `{}`", &field.name)); + } + + if !tags.insert(tag) { + return Err(format!("duplicate field tag: {}", tag)); + } + } + + Ok(()) +} diff --git a/stdtx/src/schema/value_type.rs b/stdtx/src/schema/value_type.rs new file mode 100644 index 00000000..67cd636f --- /dev/null +++ b/stdtx/src/schema/value_type.rs @@ -0,0 +1,63 @@ +//! Types of values that can be present in an `sdk.Msg` + +use crate::error::{Error, ErrorKind}; +use anomaly::fail; +use serde::{de, Deserialize}; +use std::{ + fmt::{self, Display}, + str::FromStr, +}; + +/// Types of Amino values which can be included in a [`sdk.Msg`] +/// +/// [`sdk.Msg`]: https://godoc.org/github.com/cosmos/cosmos-sdk/types#Msg +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ValueType { + /// `sdk.AccAddress`: Cosmos SDK account addresses + /// + SdkAccAddress, + + /// `sdk.Dec`: Cosmos SDK decimals + /// + SdkDecimal, + + /// `sdk.ValAddress`: Cosmos SDK validator addresses + /// + SdkValAddress, + + /// Strings + String, +} + +impl Display for ValueType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + ValueType::SdkAccAddress => "sdk.AccAddress", + ValueType::SdkDecimal => "sdk.Dec", + ValueType::SdkValAddress => "sdk.ValAddress", + ValueType::String => "string", + }) + } +} + +impl FromStr for ValueType { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "sdk.AccAddress" => ValueType::SdkAccAddress, + "sdk.Dec" => ValueType::SdkDecimal, + "sdk.ValAddress" => ValueType::SdkValAddress, + "string" => ValueType::String, + _ => fail!(ErrorKind::Parse, "unknown value type: `{}`", s), + }) + } +} + +impl<'de> Deserialize<'de> for ValueType { + fn deserialize>(deserializer: D) -> Result { + use de::Error; + let s = String::deserialize(deserializer)?; + s.parse().map_err(D::Error::custom) + } +} diff --git a/stdtx/src/type_name.rs b/stdtx/src/type_name.rs new file mode 100644 index 00000000..e28468fd --- /dev/null +++ b/stdtx/src/type_name.rs @@ -0,0 +1,87 @@ +//! Amino type names + +use crate::error::{Error, ErrorKind}; +use anomaly::fail; +use serde::{de, Deserialize}; +use sha2::{Digest, Sha256}; +use std::{ + convert::TryFrom, + fmt::{self, Display}, + str::FromStr, +}; + +/// Name of an Amino type +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct TypeName(String); + +impl TypeName { + /// Create a new `sdk.Msg` type name + pub fn new(name: impl AsRef) -> Result { + name.as_ref().parse() + } + + /// Borrow this [`TypeName`] as a string + pub fn as_str(&self) -> &str { + &self.0 + } + + /// Compute the Amino prefix for this [`TypeName`] + pub fn amino_prefix(&self) -> Vec { + Sha256::digest(self.0.as_bytes()) + .iter() + .filter(|&x| *x != 0x00) + .skip(3) + .filter(|&x| *x != 0x00) + .cloned() + .take(4) + .collect() + } +} + +impl AsRef for TypeName { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl Display for TypeName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for TypeName { + fn deserialize>(deserializer: D) -> Result { + use de::Error; + let s = String::deserialize(deserializer)?; + s.parse().map_err(D::Error::custom) + } +} + +impl FromStr for TypeName { + type Err = Error; + + fn from_str(s: &str) -> Result { + for c in s.chars() { + match c { + 'A'..='Z' | 'a'..='z' | '0'..='9' | '/' | '_' => (), + _ => fail!( + ErrorKind::Parse, + "invalid character `{}` in type name: `{}`", + c, + s + ), + } + } + + Ok(TypeName(s.to_owned())) + } +} + +impl TryFrom<&str> for TypeName { + type Error = Error; + + fn try_from(s: &str) -> Result { + s.parse() + } +} diff --git a/stdtx/tests/integration.rs b/stdtx/tests/integration.rs new file mode 100644 index 00000000..fd0be892 --- /dev/null +++ b/stdtx/tests/integration.rs @@ -0,0 +1,19 @@ +//! Integration tests + +use stdtx::Schema; + +/// Path to an example schema TOML file +const EXAMPLE_SCHEMA: &str = "tests/support/example_schema.toml"; + +/// Load an example [`Schema`] from a TOML file +#[test] +fn load_schema() { + let schema = Schema::load_toml(EXAMPLE_SCHEMA).unwrap(); + assert_eq!(schema.definitions().len(), 2); + + for definition in schema.definitions() { + for (i, field) in definition.fields().iter().enumerate() { + assert_eq!(i + 1, field.tag() as usize); + } + } +} diff --git a/stdtx/tests/support/example_schema.toml b/stdtx/tests/support/example_schema.toml new file mode 100644 index 00000000..4293f109 --- /dev/null +++ b/stdtx/tests/support/example_schema.toml @@ -0,0 +1,32 @@ +# Example StdTx message schema definition. +# +# Message types taken from Terra's oracle voter transactions: +# + +# StdTx namespace for schema definitions +# (e.g. `cosmos-sdk/StdTx` for Cosmos SDK) +namespace = "core/StdTx" + +# Bech32 address prefixes +acc_prefix = "terra" +val_prefix = "terravaloper" + +[[definition]] +type_name = "oracle/MsgExchangeRatePrevote" +fields = [ + { name = "hash", type = "string" }, + { name = "denom", type = "string" }, + { name = "feeder", type = "sdk.AccAddress" }, + { name = "validator", type = "sdk.ValAddress" }, +] + +[[definition]] +type_name = "oracle/MsgExchangeRateVote" +fields = [ + # explicit field tag example - will start from "1" otherwise + { name = "exchange_rate", type = "sdk.Dec", tag = 1 }, + { name = "salt", type = "string" }, + { name = "denom", type = "string" }, + { name = "feeder", type = "sdk.AccAddress" }, + { name = "validator", type = "sdk.ValAddress" }, +]