From 4c9bdc23d89913a6ab4c10b2bbe45f4d3a1939d2 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 21 Jun 2023 01:00:09 -0600 Subject: [PATCH] Add fuzzing support with docs; RawVal comparison proptests (#957) ### What Preliminary support for fuzzing Soroban contracts with [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz/). This patch is primarily concerned with introducing a pattern for implementing the [Arbitrary](https://docs.rs/arbitrary/latest/arbitrary/) trait, by which fuzz tests generate semi-random Rust values. This is a new revision of previous pr https://github.com/stellar/rs-soroban-sdk/pull/878. Little has changed functionally since that pr. This patch additionally uses the Arbitrary impls with [`proptest-arbitrary-interop` crate](https://github.com/graydon/proptest-arbitrary-interop) to add proptests that help ensure that: RawVals and ScVals can be converted between each other and their their comparison functions are equivalent, which closes https://github.com/stellar/rs-soroban-env/issues/743. This patch introduces the SorobanArbitrary trait which looks like this: ```rust pub trait SorobanArbitrary: TryFromVal + IntoVal + TryFromVal { type Prototype: for<'a> Arbitrary<'a>; } ``` Basically every type relevant to soroban contracts implements (or should) this trait, including i32, u32, i64, u64, i128, u128, (), bool, I256Val, U256Val, Error, Bytes, BytesN, Vec, Map, Address, Symbol, TimepointVal, DurationVal. The `#[contracttype]` macro automatically derives an implementation, along with a type that implements `SorobanArbitrary::Prototype`. In use the trait looks like ```rust use soroban_sdk::{Address, Env, Vec}; use soroban_sdk::contracttype; use soroban_sdk::arbitrary::{Arbitrary, SorobanArbitrary}; use std::vec::Vec as RustVec; #[derive(Arbitrary, Debug)] struct TestInput { deposit_amount: i128, claim_address:
::Prototype, time_bound: ::Prototype, } #[contracttype] pub struct TimeBound { pub kind: TimeBoundKind, pub timestamp: u64, } #[contracttype] pub enum TimeBoundKind { Before, After, } fuzz_target!(|input: TestInput| { let env = Env::default(); let claim_address: Address = input.claim_address.into_val(&env); let time_bound: TimeBound = input.time_bound.into_val(&env). // fuzz the program based on the input }); ``` A more complete example is at https://github.com/brson/rs-soroban-sdk/blob/val-fuzz/soroban-sdk/fuzz/fuzz_targets/fuzz_rawval_cmp.rs ### Why This patch assumes it is desirable to fuzz Soroban contracts with cargo-fuzz. Soroban reference types can only be constructed with an `Env`, but the `Arbitrary` trait constructs values from nothing but bytes. The `SorobanArbitrary` trait provides a pattern to bridge this gap, expecting fuzz tests to construct `SorobanArbitrary::Prototype` types, and then convert them to their final type with `FromVal`/`IntoVal`. There are a lot of docs here and hopefully they explain what's going on well enough. ### fuzz_catch_panic This patch also introduces a helper function, `fuzz_catch_panic`, which is built off of the `call_with_suppressed_panic_hook` function in soroban-env-host. The `fuzz_target!` macro overrides the Rust panic hook to abort on panic, assuming all panics are bugs, but Soroban contracts fail by panicking, and I have found it desirable to fuzz failing contracts. `fuzz_catch_panic` temporarily prevents the fuzzer from aborting on panic. ### Known limitations The introduction of SorobanArbitrary requires a bunch of extra documentation to explain why Soroban contracts can't just use the stock Arbitrary trait. As an alternative to this approach, we could instead expect users to construct XDR types, not SorobanArbitrary::Prototype types, and convert those to RawVals. I have been assuming that contract authors should rather concern themselves with contract types, and not the serialization types; and presently there are a number of XDR types that have values which can't be converted to contract types. The SorobanArbitrary::Prototype approach does allow more flexibility in the generation of contract types, e.g. we can generate some adversarial types like invalid object references and bad tags. Contracts must use `IntoVal` to create the final types, but these traits are under-constrained for this purpose and always require type hints: ```rust fuzz_target!(|input:
::Prototype| { let env = Env::default(); let address: Address = input.into_val(&env); // fuzz the program based on the input }); ``` This is quite unfortunate because it means the real type must be named twice. This patch introduces a new private module `arbitrary_extra` which simply defines a bunch of new conversions like ```rust impl TryFromVal for u32 { type Error = ConversionError; fn try_from_val(_env: &Env, v: &u32) -> Result { Ok(*v) } } ``` These conversions are required by `SorobanArbitrary`, which is only defined when `cfg(feature = "testutils")`; the `arbitrary_extra` module defines these conversions for all cfgs to ensure type inference is always the same, but the impls are probably useless outside of the SorobanArbitrary prototype pattern. Crates that use `#[contracttype]` need to define a "testutils" feature if they want to use Arbitrary. This patch doesn't generate "adversarial" values, which might include: - RawVals with bad tags - objects that have invalid references - objects that are reused multiple times - deeply nested vecs and maps - vecs and maps that contain heterogenous element types The arbitrary module has unit tests, and these help especially ensure the macros for custom types are correct, but the tests are not exhaustive. --------- Co-authored-by: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Co-authored-by: Graydon Hoare --- .github/workflows/rust.yml | 12 + Cargo.lock | 255 ++- Cargo.toml | 1 + Makefile | 3 + deny.toml | 8 + soroban-sdk-macros/Cargo.toml | 3 + soroban-sdk-macros/src/arbitrary.rs | 344 ++++ soroban-sdk-macros/src/derive_enum.rs | 7 +- soroban-sdk-macros/src/derive_enum_int.rs | 7 +- soroban-sdk-macros/src/derive_struct.rs | 7 +- soroban-sdk-macros/src/derive_struct_tuple.rs | 7 +- soroban-sdk-macros/src/lib.rs | 20 +- soroban-sdk/Cargo.toml | 7 +- soroban-sdk/src/arbitrary.rs | 1233 ++++++++++++++ soroban-sdk/src/arbitrary_extra.rs | 71 + soroban-sdk/src/lib.rs | 3 + soroban-sdk/src/tests.rs | 2 + soroban-sdk/src/tests/proptest_scval_cmp.rs | 121 ++ soroban-sdk/src/tests/proptest_val_cmp.rs | 135 ++ soroban-sdk/src/testutils.rs | 2 +- tests/fuzz/Cargo.toml | 25 + tests/fuzz/fuzz/.gitignore | 4 + tests/fuzz/fuzz/Cargo.lock | 1460 +++++++++++++++++ tests/fuzz/fuzz/Cargo.toml | 26 + tests/fuzz/fuzz/fuzz_targets/fuzz_target_1.rs | 25 + tests/fuzz/src/lib.rs | 13 + 26 files changed, 3784 insertions(+), 17 deletions(-) create mode 100644 soroban-sdk-macros/src/arbitrary.rs create mode 100644 soroban-sdk/src/arbitrary.rs create mode 100644 soroban-sdk/src/arbitrary_extra.rs create mode 100644 soroban-sdk/src/tests/proptest_scval_cmp.rs create mode 100644 soroban-sdk/src/tests/proptest_val_cmp.rs create mode 100644 tests/fuzz/Cargo.toml create mode 100644 tests/fuzz/fuzz/.gitignore create mode 100644 tests/fuzz/fuzz/Cargo.lock create mode 100644 tests/fuzz/fuzz/Cargo.toml create mode 100644 tests/fuzz/fuzz/fuzz_targets/fuzz_target_1.rs create mode 100644 tests/fuzz/src/lib.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 838da72e8..090b068c1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -70,6 +70,18 @@ jobs: - run: cargo hack --feature-powerset --exclude-features docs build --target ${{ matrix.sys.target }} - run: cargo hack --feature-powerset --ignore-unknown-features --features testutils --exclude-features docs test --target ${{ matrix.sys.target }} + build-fuzz: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: stellar/actions/rust-cache@main + - run: rustup install nightly + - uses: stellar/binaries@v15 + with: + name: cargo-fuzz + version: 0.11.2 + - run: make build-fuzz + docs: runs-on: ubuntu-latest steps: diff --git a/Cargo.lock b/Cargo.lock index 369e11718..da1b190bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,6 +92,27 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "block-buffer" version = "0.9.0" @@ -356,7 +377,7 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", - "rand", + "rand 0.7.3", "serde", "sha2 0.9.9", "zeroize", @@ -387,12 +408,42 @@ dependencies = [ "zeroize", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "ethnum" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0198b9d0078e0f30dedc7acbb21c974e838fc8fae3ee170128658a98cb2c1c04" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "ff" version = "0.13.0" @@ -467,6 +518,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -531,12 +588,32 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "intx" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f38a50a899dc47a6d0ed5508e7f601a2e34c3a85303514b5d137f3c10a0c75" +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + [[package]] name = "itertools" version = "0.10.5" @@ -584,6 +661,12 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.146" @@ -596,6 +679,12 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "log" version = "0.4.19" @@ -656,6 +745,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -741,6 +831,42 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "proptest-arbitrary-interop" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1981e49bd2432249da8b0e11e5557099a8e74690d6b94e721f7dc0bb7f3555f" +dependencies = [ + "arbitrary", + "proptest", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.28" @@ -758,11 +884,22 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", "libc", - "rand_chacha", + "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -773,6 +910,16 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -800,6 +947,30 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "rfc6979" version = "0.4.0" @@ -816,6 +987,32 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustix" +version = "0.37.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.13" @@ -990,8 +1187,8 @@ dependencies = [ "num-derive", "num-integer", "num-traits", - "rand", - "rand_chacha", + "rand 0.7.3", + "rand_chacha 0.2.2", "sha2 0.9.9", "sha3", "soroban-env-common", @@ -1043,10 +1240,13 @@ dependencies = [ name = "soroban-sdk" version = "0.8.4" dependencies = [ + "arbitrary", "bytes-lit", "ed25519-dalek", "hex", - "rand", + "proptest", + "proptest-arbitrary-interop", + "rand 0.7.3", "soroban-env-guest", "soroban-env-host", "soroban-ledger-snapshot", @@ -1207,6 +1407,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +dependencies = [ + "autocfg", + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + [[package]] name = "test_add_i128" version = "0.8.4" @@ -1277,6 +1491,13 @@ dependencies = [ "soroban-sdk", ] +[[package]] +name = "test_fuzz" +version = "0.8.4" +dependencies = [ + "soroban-sdk", +] + [[package]] name = "test_import_contract" version = "0.8.4" @@ -1358,6 +1579,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.9" @@ -1370,6 +1597,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -1490,6 +1726,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 292502c08..42c0fd307 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ "tests/errors", "tests/alloc", "tests/auth", + "tests/fuzz", ] [workspace.package] diff --git a/Makefile b/Makefile index e2d344b03..f9621ef5f 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,9 @@ check: build fmt cargo hack --feature-powerset --exclude-features docs check cargo hack check --release --target wasm32-unknown-unknown +build-fuzz: + cd tests/fuzz/fuzz && cargo +nightly fuzz check + readme: cd soroban-sdk \ && cargo +nightly rustdoc -- -Zunstable-options -wjson \ diff --git a/deny.toml b/deny.toml index 6ed140d8e..b095aa443 100644 --- a/deny.toml +++ b/deny.toml @@ -249,6 +249,14 @@ deny = [ # Certain crates/versions that will be skipped when doing duplicate detection. skip = [ #{ name = "ansi_term", version = "=0.11.0" }, + + # ed25519-dalek and proptest have conflicting rand deps, + # where ed25519-dalek is on an older revision. + # proptest is only used as a dev-dependency, + # so this conflict should not cause extra bloat to contracts. + # Below are the newer version numbers required by proptest. + { name = "rand", version = "0.8" }, + { name = "rand_chacha", version = "0.3" }, ] # Similarly to `skip` allows you to skip certain crates during duplicate # detection. Unlike skip, it also includes the entire tree of transitive diff --git a/soroban-sdk-macros/Cargo.toml b/soroban-sdk-macros/Cargo.toml index 5143fcef4..25348ea2b 100644 --- a/soroban-sdk-macros/Cargo.toml +++ b/soroban-sdk-macros/Cargo.toml @@ -25,3 +25,6 @@ proc-macro2 = "1.0" itertools = "0.10.3" darling = "0.20.0" sha2 = "0.9.9" + +[features] +testutils = [] diff --git a/soroban-sdk-macros/src/arbitrary.rs b/soroban-sdk-macros/src/arbitrary.rs new file mode 100644 index 000000000..b9c5322f9 --- /dev/null +++ b/soroban-sdk-macros/src/arbitrary.rs @@ -0,0 +1,344 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::{DataEnum, DataStruct, Ident, Path, Visibility}; + +pub fn derive_arbitrary_struct( + path: &Path, + vis: &Visibility, + ident: &Ident, + data: &DataStruct, +) -> TokenStream2 { + derive_arbitrary_struct_common(path, vis, ident, data, FieldType::Named) +} + +pub fn derive_arbitrary_struct_tuple( + path: &Path, + vis: &Visibility, + ident: &Ident, + data: &DataStruct, +) -> TokenStream2 { + derive_arbitrary_struct_common(path, vis, ident, data, FieldType::Unnamed) +} + +enum FieldType { + Named, + Unnamed, +} + +fn derive_arbitrary_struct_common( + path: &Path, + vis: &Visibility, + ident: &Ident, + data: &DataStruct, + field_type: FieldType, +) -> TokenStream2 { + let arbitrary_type_ident = format_ident!("Arbitrary{}", ident); + + let arbitrary_type_fields: Vec = data + .fields + .iter() + .map(|field| { + let field_type = &field.ty; + match &field.ident { + Some(ident) => { + quote! { + #ident: <#field_type as #path::arbitrary::SorobanArbitrary>::Prototype + } + } + None => { + quote! { + <#field_type as #path::arbitrary::SorobanArbitrary>::Prototype + } + } + } + }) + .collect(); + + let field_conversions: Vec = data + .fields + .iter() + .enumerate() + .map(|(i, field)| match &field.ident { + Some(ident) => { + quote! { + #ident: #path::IntoVal::into_val(&v.#ident, env) + } + } + None => { + let i = syn::Index::from(i); + quote! { + #path::IntoVal::into_val(&v.#i, env) + } + } + }) + .collect(); + + let arbitrary_type_decl = match field_type { + FieldType::Named => quote! { + struct #arbitrary_type_ident { + #(#arbitrary_type_fields,)* + } + }, + FieldType::Unnamed => quote! { + struct #arbitrary_type_ident ( + #(#arbitrary_type_fields,)* + ); + }, + }; + + let arbitrary_ctor = match field_type { + FieldType::Named => quote! { + #ident { + #(#field_conversions,)* + } + }, + FieldType::Unnamed => quote! { + #ident ( + #(#field_conversions,)* + ) + }, + }; + + quote_arbitrary( + path, + vis, + ident, + arbitrary_type_ident, + arbitrary_type_decl, + arbitrary_ctor, + ) +} + +pub fn derive_arbitrary_enum( + path: &Path, + vis: &Visibility, + ident: &Ident, + data: &DataEnum, +) -> TokenStream2 { + let arbitrary_type_ident = format_ident!("Arbitrary{}", ident); + + let arbitrary_type_variants: Vec = data + .variants + .iter() + .map(|variant| { + let mut field_types = None; + let variant_ident = &variant.ident; + let fields: Vec = variant + .fields + .iter() + .map(|field| { + let field_type = &field.ty; + match &field.ident { + Some(ident) => { + field_types = Some(FieldType::Named); + quote! { + #ident: <#field_type as #path::arbitrary::SorobanArbitrary>::Prototype + } + } + None => { + field_types = Some(FieldType::Unnamed); + quote! { + <#field_type as #path::arbitrary::SorobanArbitrary>::Prototype + } + } + } + }) + .collect(); + match field_types { + None => { + quote! { + #variant_ident + } + }, + Some(FieldType::Named) => { + quote! { + #variant_ident { #(#fields,)* } + } + } + Some(FieldType::Unnamed) => { + quote! { + #variant_ident ( #(#fields,)* ) + } + } + } + }) + .collect(); + + let variant_conversions: Vec = data + .variants + .iter() + .map(|variant| { + let mut field_types = None; + let variant_ident = &variant.ident; + let fields: Vec = variant + .fields + .iter() + .enumerate() + .map(|(i, field)| { + match &field.ident { + Some(ident) => { + quote! { + #ident + } + } + None => { + let ident = format_ident!("field_{}", i); + quote! { + #ident + } + } + } + }) + .collect(); + let field_conversions: Vec = variant + .fields + .iter() + .enumerate() + .map(|(i, field)| { + match &field.ident { + Some(ident) => { + field_types = Some(FieldType::Named); + quote! { + #ident: #path::IntoVal::into_val(#ident, env) + } + } + None => { + field_types = Some(FieldType::Unnamed); + let ident = format_ident!("field_{}", i); + quote! { + #path::IntoVal::into_val(#ident, env) + } + } + } + }) + .collect(); + match field_types { + None => { + quote! { + #arbitrary_type_ident::#variant_ident => #ident::#variant_ident + } + }, + Some(FieldType::Named) => { + quote! { + #arbitrary_type_ident::#variant_ident { #(#fields,)* } => #ident::#variant_ident { #(#field_conversions,)* } + } + } + Some(FieldType::Unnamed) => { + quote! { + #arbitrary_type_ident::#variant_ident ( #(#fields,)* ) => #ident::#variant_ident ( #(#field_conversions,)* ) + } + } + } + }) + .collect(); + + let arbitrary_type_decl = quote! { + enum #arbitrary_type_ident { + #(#arbitrary_type_variants,)* + } + }; + let arbitrary_ctor = quote! { + match v { + #(#variant_conversions,)* + } + }; + + quote_arbitrary( + path, + vis, + ident, + arbitrary_type_ident, + arbitrary_type_decl, + arbitrary_ctor, + ) +} + +pub fn derive_arbitrary_enum_int( + path: &Path, + vis: &Visibility, + ident: &Ident, + data: &DataEnum, +) -> TokenStream2 { + let arbitrary_type_ident = format_ident!("Arbitrary{}", ident); + + let arbitrary_type_variants: Vec = data + .variants + .iter() + .map(|variant| { + let variant_ident = &variant.ident; + quote! { + #variant_ident + } + }) + .collect(); + + let variant_conversions: Vec = data + .variants + .iter() + .map(|variant| { + let variant_ident = &variant.ident; + quote! { + #arbitrary_type_ident::#variant_ident => #ident::#variant_ident + } + }) + .collect(); + + let arbitrary_type_decl = quote! { + enum #arbitrary_type_ident { + #(#arbitrary_type_variants,)* + } + }; + let arbitrary_ctor = quote! { + match v { + #(#variant_conversions,)* + } + }; + + quote_arbitrary( + path, + vis, + ident, + arbitrary_type_ident, + arbitrary_type_decl, + arbitrary_ctor, + ) +} + +fn quote_arbitrary( + path: &Path, + vis: &Visibility, + ident: &Ident, + arbitrary_type_ident: Ident, + arbitrary_type_decl: TokenStream2, + arbitrary_ctor: TokenStream2, +) -> TokenStream2 { + if !cfg!(any(test, feature = "testutils")) { + return quote!(); + } + quote! { + // This allows us to create a scope to import std and arbitrary, while + // also keeping everything from the current scope. This is better than a + // module because: modules inside functions have surprisingly + // inconsistent scoping rules and visibility management is harder. + #[cfg(any(test, feature = "testutils"))] + const _: () = { + // derive(Arbitrary) expects these two to be in scope + use #path::arbitrary::std; + use #path::arbitrary::arbitrary; + + #[derive(#path::arbitrary::arbitrary::Arbitrary, Debug)] + #vis #arbitrary_type_decl + + impl #path::arbitrary::SorobanArbitrary for #ident { + type Prototype = #arbitrary_type_ident; + } + + impl #path::TryFromVal<#path::Env, #arbitrary_type_ident> for #ident { + type Error = #path::ConversionError; + fn try_from_val(env: &#path::Env, v: &#arbitrary_type_ident) -> std::result::Result { + Ok(#arbitrary_ctor) + } + } + }; + } +} diff --git a/soroban-sdk-macros/src/derive_enum.rs b/soroban-sdk-macros/src/derive_enum.rs index 58f066477..b36f2bc41 100644 --- a/soroban-sdk-macros/src/derive_enum.rs +++ b/soroban-sdk-macros/src/derive_enum.rs @@ -1,7 +1,7 @@ use itertools::MultiUnzip; use proc_macro2::{Literal, TokenStream as TokenStream2}; use quote::{format_ident, quote}; -use syn::{spanned::Spanned, Attribute, DataEnum, Error, Fields, Ident, Path}; +use syn::{spanned::Spanned, Attribute, DataEnum, Error, Fields, Ident, Path, Visibility}; use stellar_xdr::{ Error as XdrError, ScSpecEntry, ScSpecTypeDef, ScSpecUdtUnionCaseTupleV0, ScSpecUdtUnionCaseV0, @@ -12,6 +12,7 @@ use crate::{doc::docs_from_attrs, map_type::map_type}; pub fn derive_type_enum( path: &Path, + vis: &Visibility, enum_ident: &Ident, attrs: &[Attribute], data: &DataEnum, @@ -160,6 +161,8 @@ pub fn derive_type_enum( None }; + let arbitrary_tokens = crate::arbitrary::derive_arbitrary_enum(path, vis, enum_ident, data); + // Output. quote! { #spec_gen @@ -262,6 +265,8 @@ pub fn derive_type_enum( (&val).try_into() } } + + #arbitrary_tokens } } diff --git a/soroban-sdk-macros/src/derive_enum_int.rs b/soroban-sdk-macros/src/derive_enum_int.rs index ca68fe688..ef495fd35 100644 --- a/soroban-sdk-macros/src/derive_enum_int.rs +++ b/soroban-sdk-macros/src/derive_enum_int.rs @@ -2,7 +2,7 @@ use itertools::MultiUnzip; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; use stellar_xdr::{ScSpecUdtEnumV0, StringM}; -use syn::{spanned::Spanned, Attribute, DataEnum, Error, ExprLit, Ident, Lit, Path}; +use syn::{spanned::Spanned, Attribute, DataEnum, Error, ExprLit, Ident, Lit, Path, Visibility}; use stellar_xdr::{ScSpecEntry, ScSpecUdtEnumCaseV0, WriteXdr}; @@ -12,6 +12,7 @@ use crate::doc::docs_from_attrs; pub fn derive_type_enum_int( path: &Path, + vis: &Visibility, enum_ident: &Ident, attrs: &[Attribute], data: &DataEnum, @@ -89,6 +90,8 @@ pub fn derive_type_enum_int( None }; + let arbitrary_tokens = crate::arbitrary::derive_arbitrary_enum_int(path, vis, enum_ident, data); + // Output. quote! { #spec_gen @@ -149,5 +152,7 @@ pub fn derive_type_enum_int( Ok((self as u32).into()) } } + + #arbitrary_tokens } } diff --git a/soroban-sdk-macros/src/derive_struct.rs b/soroban-sdk-macros/src/derive_struct.rs index 3bb430b4c..1da14dfde 100644 --- a/soroban-sdk-macros/src/derive_struct.rs +++ b/soroban-sdk-macros/src/derive_struct.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use proc_macro2::{Literal, TokenStream as TokenStream2}; use quote::{format_ident, quote}; -use syn::{Attribute, DataStruct, Error, Ident, Path}; +use syn::{Attribute, DataStruct, Error, Ident, Path, Visibility}; use stellar_xdr::{ ScSpecEntry, ScSpecTypeDef, ScSpecUdtStructFieldV0, ScSpecUdtStructV0, StringM, WriteXdr, @@ -16,6 +16,7 @@ use crate::{doc::docs_from_attrs, map_type::map_type}; pub fn derive_type_struct( path: &Path, + vis: &Visibility, ident: &Ident, attrs: &[Attribute], data: &DataStruct, @@ -100,6 +101,8 @@ pub fn derive_type_struct( None }; + let arbitrary_tokens = crate::arbitrary::derive_arbitrary_struct(path, vis, ident, data); + // Output. quote! { #spec_gen @@ -200,5 +203,7 @@ pub fn derive_type_struct( (&val).try_into() } } + + #arbitrary_tokens } } diff --git a/soroban-sdk-macros/src/derive_struct_tuple.rs b/soroban-sdk-macros/src/derive_struct_tuple.rs index 52db345f1..9586fdc8d 100644 --- a/soroban-sdk-macros/src/derive_struct_tuple.rs +++ b/soroban-sdk-macros/src/derive_struct_tuple.rs @@ -1,7 +1,7 @@ use itertools::MultiUnzip; use proc_macro2::{Literal, TokenStream as TokenStream2}; use quote::{format_ident, quote}; -use syn::{Attribute, DataStruct, Error, Ident, Path}; +use syn::{Attribute, DataStruct, Error, Ident, Path, Visibility}; use stellar_xdr::{ ScSpecEntry, ScSpecTypeDef, ScSpecUdtStructFieldV0, ScSpecUdtStructV0, StringM, WriteXdr, @@ -11,6 +11,7 @@ use crate::{doc::docs_from_attrs, map_type::map_type}; pub fn derive_type_struct_tuple( path: &Path, + vis: &Visibility, ident: &Ident, attrs: &[Attribute], data: &DataStruct, @@ -87,6 +88,8 @@ pub fn derive_type_struct_tuple( None }; + let arbitrary_tokens = crate::arbitrary::derive_arbitrary_struct_tuple(path, vis, ident, data); + // Output. quote! { #spec_gen @@ -186,5 +189,7 @@ pub fn derive_type_struct_tuple( (&val).try_into() } } + + #arbitrary_tokens } } diff --git a/soroban-sdk-macros/src/lib.rs b/soroban-sdk-macros/src/lib.rs index e54aefc77..8b591c7d2 100644 --- a/soroban-sdk-macros/src/lib.rs +++ b/soroban-sdk-macros/src/lib.rs @@ -1,5 +1,6 @@ extern crate proc_macro; +mod arbitrary; mod derive_client; mod derive_enum; mod derive_enum_int; @@ -255,6 +256,7 @@ pub fn contracttype(metadata: TokenStream, input: TokenStream) -> TokenStream { Err(e) => return e.write_errors().into(), }; let input = parse_macro_input!(input as DeriveInput); + let vis = &input.vis; let ident = &input.ident; let attrs = &input.attrs; // If the export argument has a value, do as it instructs regarding @@ -267,11 +269,17 @@ pub fn contracttype(metadata: TokenStream, input: TokenStream) -> TokenStream { let derived = match &input.data { Data::Struct(s) => match s.fields { Fields::Named(_) => { - derive_type_struct(&args.crate_path, ident, attrs, s, gen_spec, &args.lib) - } - Fields::Unnamed(_) => { - derive_type_struct_tuple(&args.crate_path, ident, attrs, s, gen_spec, &args.lib) + derive_type_struct(&args.crate_path, vis, ident, attrs, s, gen_spec, &args.lib) } + Fields::Unnamed(_) => derive_type_struct_tuple( + &args.crate_path, + vis, + ident, + attrs, + s, + gen_spec, + &args.lib, + ), Fields::Unit => Error::new( s.fields.span(), "unit structs are not supported as contract types", @@ -286,9 +294,9 @@ pub fn contracttype(metadata: TokenStream, input: TokenStream) -> TokenStream { .filter(|v| v.discriminant.is_some()) .count(); if count_of_int_variants == 0 { - derive_type_enum(&args.crate_path, ident, attrs, e, gen_spec, &args.lib) + derive_type_enum(&args.crate_path, vis, ident, attrs, e, gen_spec, &args.lib) } else if count_of_int_variants == count_of_variants { - derive_type_enum_int(&args.crate_path, ident, attrs, e, gen_spec, &args.lib) + derive_type_enum_int(&args.crate_path, vis, ident, attrs, e, gen_spec, &args.lib) } else { Error::new(input.span(), "enums are supported as contract types only when all variants have an explicit integer literal, or when all variants are unit or single field") .to_compile_error() diff --git a/soroban-sdk/Cargo.toml b/soroban-sdk/Cargo.toml index cf782aa62..454dac12a 100644 --- a/soroban-sdk/Cargo.toml +++ b/soroban-sdk/Cargo.toml @@ -24,21 +24,26 @@ soroban-env-guest = { workspace = true } soroban-env-host = { workspace = true, features = [] } soroban-ledger-snapshot = { workspace = true } stellar-strkey = { workspace = true } +arbitrary = { version = "1.1.3", features = ["derive"], optional = true } ed25519-dalek = { version = "1.0.1", optional = true } # match the version of rand used in dalek rand = "0.7.3" [dev-dependencies] +soroban-sdk-macros = { workspace = true, features = ["testutils"] } soroban-env-host = { workspace = true, features = ["testutils"] } stellar-xdr = { workspace = true, features = ["next", "std"] } soroban-spec = { workspace = true } ed25519-dalek = "1.0.1" rand = "0.7.3" hex = "0.4.3" +arbitrary = { version = "1.1.3", features = ["derive"] } +proptest = "1.2.0" +proptest-arbitrary-interop = "0.1.0" [features] alloc = [] -testutils = ["soroban-env-host/testutils", "dep:ed25519-dalek"] +testutils = ["soroban-sdk-macros/testutils", "soroban-env-host/testutils", "dep:ed25519-dalek", "dep:arbitrary"] docs = [] [package.metadata.docs.rs] diff --git a/soroban-sdk/src/arbitrary.rs b/soroban-sdk/src/arbitrary.rs new file mode 100644 index 000000000..efdf4f943 --- /dev/null +++ b/soroban-sdk/src/arbitrary.rs @@ -0,0 +1,1233 @@ +//! Support for fuzzing Soroban contracts with [`cargo-fuzz`]. +//! +//! This module provides a pattern for generating Soroban contract types for the +//! purpose of fuzzing Soroban contracts. It is focused on implementing the +//! [`Arbitrary`] trait that `cargo-fuzz` relies on to feed fuzzers with +//! generated Rust values. +//! +//! [`cargo-fuzz`]: https://github.com/rust-fuzz/cargo-fuzz/ +//! [`Arbitrary`]: ::arbitrary::Arbitrary +//! +//! This module +//! +//! - defines the [`SorobanArbitrary`] trait, +//! - defines the [`fuzz_catch_panic`] helper, +//! - reexports the [`arbitrary`] crate and the [`Arbitrary`] type. +//! +//! This module is only available when the "testutils" Cargo feature is defined. +//! +//! +//! ## About `cargo-fuzz` and `Arbitrary` +//! +//! In its basic operation `cargo-fuzz` fuzz generates raw bytes and feeds them +//! to a program-dependent fuzzer designed to exercise a program, in our case a +//! Soroban contract. +//! +//! `cargo-fuzz` programs declare their entry points with a macro: +//! +//! ``` +//! # macro_rules! fuzz_target { +//! # (|$data:ident: $dty: ty| $body:block) => { }; +//! # } +//! fuzz_target!(|input: &[u8]| { +//! // feed bytes to the program +//! }); +//! ``` +//! +//! More sophisticated fuzzers accept not bytes but Rust types: +//! +//! ``` +//! # use arbitrary::Arbitrary; +//! # macro_rules! fuzz_target { +//! # (|$data:ident: $dty: ty| $body:block) => { }; +//! # } +//! #[derive(Arbitrary, Debug)] +//! struct FuzzDeposit { +//! deposit_amount: i128, +//! } +//! +//! fuzz_target!(|input: FuzzDeposit| { +//! // fuzz the program based on the input +//! }); +//! ``` +//! +//! Types accepted as input to `fuzz_target` must implement the `Arbitrary` trait, +//! which transforms bytes to Rust types. +//! +//! +//! ## The `SorobanArbitrary` trait +//! +//! Soroban types are managed by the host environment, and so must be created +//! from an [`Env`] value; `Arbitrary` values though must be created from +//! nothing but bytes. The [`SorobanArbitrary`] trait, implemented for all +//! Soroban contract types, exists to bridge this gap: it defines a _prototype_ +//! pattern whereby the `fuzz_target` macro creates prototype values that the +//! fuzz program can convert to contract values with the standard soroban +//! conversion traits, [`FromVal`] or [`IntoVal`]. +//! +//! [`Env`]: crate::Env +//! [`FromVal`]: crate::FromVal +//! [`IntoVal`]: crate::IntoVal +//! +//! The types of prototypes are identified by the associated type, +//! [`SorobanArbitrary::Prototype`]: +//! +//! ``` +//! # use soroban_sdk::arbitrary::Arbitrary; +//! # use soroban_sdk::{TryFromVal, IntoVal, Val, Env}; +//! pub trait SorobanArbitrary: +//! TryFromVal + IntoVal + TryFromVal +//! { +//! type Prototype: for <'a> Arbitrary<'a>; +//! } +//! ``` +//! +//! Types that implement `SorobanArbitrary` include: +//! +//! - `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `I256`, `U256`, `()`, and `bool`, +//! - [`Error`], +//! - [`Bytes`], [`BytesN`], [`Vec`], [`Map`], +//! - [`Address`], [`Symbol`], +//! - [`Val`], +//! +//! [`I256`]: crate::I256 +//! [`U256`]: crate::U256 +//! [`Error`]: crate::Error +//! [`Bytes`]: crate::Bytes +//! [`BytesN`]: crate::BytesN +//! [`Vec`]: crate::Vec +//! [`Map`]: crate::Map +//! [`Address`]: crate::Address +//! [`Symbol`]: crate::Symbol +//! [`Val`]: crate::Val +//! +//! All user-defined contract types, those with the [`contracttype`] attribute, +//! automatically derive `SorobanArbitrary`. Note that `SorobanArbitrary` is +//! only derived when the "testutils" Cargo feature is active. This implies +//! that, in general, to make a Soroban contract fuzzable, the contract crate +//! must define a "testutils" Cargo feature, that feature should turn on the +//! "soroban-sdk/testutils" feature, and the fuzz test, which is its own crate, +//! must turn that feature on. +//! +//! [`contracttype`]: crate::contracttype +//! +//! +//! ## Example: take a Soroban `Vec` of `Address` as fuzzer input +//! +//! ``` +//! # macro_rules! fuzz_target { +//! # (|$data:ident: $dty: ty| $body:block) => { }; +//! # } +//! use soroban_sdk::{Address, Env, Vec}; +//! use soroban_sdk::arbitrary::SorobanArbitrary; +//! +//! fuzz_target!(|input: as SorobanArbitrary>::Prototype| { +//! let env = Env::default(); +//! let addresses: Vec
= input.into_val(&env); +//! // fuzz the program based on the input +//! }); +//! ``` +//! +//! +//! ## Example: take a custom contract type as fuzzer input +//! +//! ``` +//! # macro_rules! fuzz_target { +//! # (|$data:ident: $dty: ty| $body:block) => { }; +//! # } +//! use soroban_sdk::{Address, Env, Vec}; +//! use soroban_sdk::contracttype; +//! use soroban_sdk::arbitrary::{Arbitrary, SorobanArbitrary}; +//! use std::vec::Vec as RustVec; +//! +//! #[derive(Arbitrary, Debug)] +//! struct TestInput { +//! deposit_amount: i128, +//! claim_address:
::Prototype, +//! time_bound: ::Prototype, +//! } +//! +//! #[contracttype] +//! pub struct TimeBound { +//! pub kind: TimeBoundKind, +//! pub timestamp: u64, +//! } +//! +//! #[contracttype] +//! pub enum TimeBoundKind { +//! Before, +//! After, +//! } +//! +//! fuzz_target!(|input: TestInput| { +//! let env = Env::default(); +//! let claim_address: Address = input.claim_address.into_val(&env); +//! let time_bound: TimeBound = input.time_bound.into_val(&env); +//! // fuzz the program based on the input +//! }); +//! ``` + +#![cfg(any(test, feature = "testutils"))] +#![cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))] + +/// A reexport of the `arbitrary` crate. +/// +/// Used by the `contracttype` macro to derive `Arbitrary`. +pub use arbitrary; + +// Used often enough in fuzz tests to want direct access to it. +pub use arbitrary::Arbitrary; + +// Used by `contracttype` +#[doc(hidden)] +pub use std; + +pub use api::*; +pub use fuzz_test_helpers::*; + +/// The traits that must be implemented on Soroban types to support fuzzing. +/// +/// These allow for ergonomic conversion from a randomly-generated "prototype" +/// that implements `Arbitrary` into `Env`-"hosted" values that are paired with an +/// `Env`. +/// +/// These traits are intended to be easy to automatically derive. +mod api { + use crate::Env; + use crate::Val; + use crate::{IntoVal, TryFromVal}; + use arbitrary::Arbitrary; + + /// An `Env`-hosted contract value that can be randomly generated. + /// + /// Types that implement `SorabanArbitrary` have an associated "prototype" + /// type that implements [`Arbitrary`]. + /// + /// This exists partly that the prototype can be named like + /// + /// ```ignore + /// fuzz_target!(|input: ::Arbitrary| { + /// ... + /// }); + /// ``` + // This also makes derivation of `SorobanArbitrary` for custom types easier + // since we depend on all fields also implementing `SorobanArbitrary`. + // + // The IntoVal + TryFromVal bounds are to satisfy + // the bounds of Vec and Map, so that collections of prototypes can be + // converted to contract types. + pub trait SorobanArbitrary: + TryFromVal + IntoVal + TryFromVal + { + /// A type that implements [`Arbitrary`] and can be converted to this + /// [`SorobanArbitrary`] type. + // NB: The `Arbitrary` bound here is not necessary for the correct use of + // `SorobanArbitrary`, but it makes the purpose clear. + type Prototype: for<'a> Arbitrary<'a>; + } +} + +/// Implementations of `soroban_sdk::arbitrary::api` for Rust scalar types. +/// +/// These types +/// +/// - do not have a distinct `Arbitrary` prototype, +/// i.e. they use themselves as the `SorobanArbitrary::Prototype` type, +/// - implement `Arbitrary` in the `arbitrary` crate, +/// - trivially implement `TryFromVal`, +/// +/// Examples: +/// +/// - `u32` +mod scalars { + use crate::arbitrary::api::*; + + impl SorobanArbitrary for () { + type Prototype = (); + } + + impl SorobanArbitrary for bool { + type Prototype = bool; + } + + impl SorobanArbitrary for u32 { + type Prototype = u32; + } + + impl SorobanArbitrary for i32 { + type Prototype = i32; + } + + impl SorobanArbitrary for u64 { + type Prototype = u64; + } + + impl SorobanArbitrary for i64 { + type Prototype = i64; + } + + impl SorobanArbitrary for u128 { + type Prototype = u128; + } + + impl SorobanArbitrary for i128 { + type Prototype = i128; + } +} + +/// Implementations of `soroban_sdk::arbitrary::api` for Soroban types that do not +/// need access to the Soroban host environment. +/// +/// These types +/// +/// - do not have a distinct `Arbitrary` prototype, +/// i.e. they use themselves as the `SorobanArbitrary::Prototype` type, +/// - implement `Arbitrary` in the `soroban-env-common` crate, +/// - trivially implement `TryFromVal`, +/// +/// Examples: +/// +/// - `Error` +mod simple { + use crate::arbitrary::api::*; + pub use crate::Error; + + impl SorobanArbitrary for Error { + type Prototype = Error; + } +} + +/// Implementations of `soroban_sdk::arbitrary::api` for Soroban types that do +/// need access to the Soroban host environment. +/// +/// These types +/// +/// - have a distinct `Arbitrary` prototype that derives `Arbitrary`, +/// - require an `Env` to be converted to their actual contract type. +/// +/// Examples: +/// +/// - `Vec` +mod objects { + use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}; + + use crate::arbitrary::api::*; + use crate::ConversionError; + use crate::{Env, IntoVal, TryFromVal}; + + use crate::xdr::{Int256Parts, ScVal, UInt256Parts}; + use crate::{Address, Bytes, BytesN, Map, String, Symbol, Val, Vec, I256, U256}; + use soroban_env_host::TryIntoVal; + + use std::string::String as RustString; + use std::vec::Vec as RustVec; + + ////////////////////////////////// + + #[derive(Arbitrary, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] + pub struct ArbitraryU256 { + parts: (u64, u64, u64, u64), + } + + impl SorobanArbitrary for U256 { + type Prototype = ArbitraryU256; + } + + impl TryFromVal for U256 { + type Error = ConversionError; + fn try_from_val(env: &Env, v: &ArbitraryU256) -> Result { + let v = ScVal::U256(UInt256Parts { + hi_hi: v.parts.0, + hi_lo: v.parts.1, + lo_hi: v.parts.2, + lo_lo: v.parts.3, + }); + let v = Val::try_from_val(env, &v)?; + v.try_into_val(env) + } + } + + ////////////////////////////////// + + #[derive(Arbitrary, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] + pub struct ArbitraryI256 { + parts: (i64, u64, u64, u64), + } + + impl SorobanArbitrary for I256 { + type Prototype = ArbitraryI256; + } + + impl TryFromVal for I256 { + type Error = ConversionError; + fn try_from_val(env: &Env, v: &ArbitraryI256) -> Result { + let v = ScVal::I256(Int256Parts { + hi_hi: v.parts.0, + hi_lo: v.parts.1, + lo_hi: v.parts.2, + lo_lo: v.parts.3, + }); + let v = Val::try_from_val(env, &v)?; + v.try_into_val(env) + } + } + + ////////////////////////////////// + + #[derive(Arbitrary, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] + pub struct ArbitraryBytes { + vec: RustVec, + } + + impl SorobanArbitrary for Bytes { + type Prototype = ArbitraryBytes; + } + + impl TryFromVal for Bytes { + type Error = ConversionError; + fn try_from_val(env: &Env, v: &ArbitraryBytes) -> Result { + Self::try_from_val(env, &v.vec.as_slice()) + } + } + + ////////////////////////////////// + + #[derive(Arbitrary, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] + pub struct ArbitraryString { + inner: RustString, + } + + impl SorobanArbitrary for String { + type Prototype = ArbitraryString; + } + + impl TryFromVal for String { + type Error = ConversionError; + fn try_from_val(env: &Env, v: &ArbitraryString) -> Result { + Self::try_from_val(env, &v.inner.as_str()) + } + } + + ////////////////////////////////// + + #[derive(Arbitrary, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] + pub struct ArbitraryBytesN { + array: [u8; N], + } + + impl SorobanArbitrary for BytesN { + type Prototype = ArbitraryBytesN; + } + + impl TryFromVal> for BytesN { + type Error = ConversionError; + fn try_from_val(env: &Env, v: &ArbitraryBytesN) -> Result { + Self::try_from_val(env, &v.array) + } + } + + ////////////////////////////////// + + #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] + pub struct ArbitrarySymbol { + s: RustString, + } + + impl<'a> Arbitrary<'a> for ArbitrarySymbol { + fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult { + let valid_chars = "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + let valid_chars = valid_chars.as_bytes(); + let mut chars = vec![]; + let len = u.int_in_range(0..=32)?; + for _ in 0..len { + let ch = u.choose(valid_chars)?; + chars.push(*ch); + } + Ok(ArbitrarySymbol { + s: RustString::from_utf8(chars).expect("utf8"), + }) + } + } + + impl SorobanArbitrary for Symbol { + type Prototype = ArbitrarySymbol; + } + + impl TryFromVal for Symbol { + type Error = ConversionError; + fn try_from_val(env: &Env, v: &ArbitrarySymbol) -> Result { + Self::try_from_val(env, &v.s.as_str()) + } + } + + ////////////////////////////////// + + #[derive(Arbitrary, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] + pub struct ArbitraryVec { + vec: RustVec, + } + + impl SorobanArbitrary for Vec + where + T: SorobanArbitrary, + { + type Prototype = ArbitraryVec; + } + + impl TryFromVal> for Vec + where + T: SorobanArbitrary, + { + type Error = ConversionError; + fn try_from_val(env: &Env, v: &ArbitraryVec) -> Result { + let mut buf: Vec = Vec::new(env); + for item in v.vec.iter() { + buf.push_back(item.into_val(env)); + } + Ok(buf) + } + } + + ////////////////////////////////// + + #[derive(Arbitrary, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] + pub struct ArbitraryMap { + map: RustVec<(K, V)>, + } + + impl SorobanArbitrary for Map + where + K: SorobanArbitrary, + V: SorobanArbitrary, + { + type Prototype = ArbitraryMap; + } + + impl TryFromVal> for Map + where + K: SorobanArbitrary, + V: SorobanArbitrary, + { + type Error = ConversionError; + fn try_from_val( + env: &Env, + v: &ArbitraryMap, + ) -> Result { + let mut map: Map = Map::new(env); + for (k, v) in v.map.iter() { + map.set(k.into_val(env), v.into_val(env)); + } + Ok(map) + } + } + + ////////////////////////////////// + + #[derive(Arbitrary, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] + pub struct ArbitraryAddress { + inner: [u8; 32], + } + + impl SorobanArbitrary for Address { + type Prototype = ArbitraryAddress; + } + + impl TryFromVal for Address { + type Error = ConversionError; + fn try_from_val(env: &Env, v: &ArbitraryAddress) -> Result { + use crate::env::xdr::{Hash, ScAddress}; + + let sc_addr = ScVal::Address(ScAddress::Contract(Hash(v.inner))); + Ok(sc_addr.into_val(env)) + } + } +} + +/// Implementations of `soroban_sdk::arbitrary::api` for `Val`. +mod composite { + use arbitrary::Arbitrary; + + use crate::arbitrary::api::*; + use crate::ConversionError; + use crate::{Env, IntoVal, TryFromVal}; + + use super::objects::*; + use super::simple::*; + use crate::{Address, Bytes, Map, String, Symbol, Val, Vec, I256, U256}; + + #[derive(Arbitrary, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] + pub enum ArbitraryVal { + Void, + Bool(bool), + Error(Error), + U32(u32), + I32(i32), + U64(u64), + I64(i64), + U128(u128), + I128(i128), + U256(ArbitraryU256), + I256(ArbitraryI256), + Bytes(ArbitraryBytes), + String(ArbitraryString), + Symbol(ArbitrarySymbol), + Vec(ArbitraryValVec), + Map(ArbitraryValMap), + Address(
::Prototype), + } + + impl SorobanArbitrary for Val { + type Prototype = ArbitraryVal; + } + + impl TryFromVal for Val { + type Error = ConversionError; + fn try_from_val(env: &Env, v: &ArbitraryVal) -> Result { + Ok(match v { + ArbitraryVal::Void => Val::VOID.into(), + ArbitraryVal::Bool(v) => v.into_val(env), + ArbitraryVal::Error(v) => v.into_val(env), + ArbitraryVal::U32(v) => v.into_val(env), + ArbitraryVal::I32(v) => v.into_val(env), + ArbitraryVal::U64(v) => v.into_val(env), + ArbitraryVal::I64(v) => v.into_val(env), + ArbitraryVal::U256(v) => { + let v: U256 = v.into_val(env); + v.into_val(env) + } + ArbitraryVal::I256(v) => { + let v: I256 = v.into_val(env); + v.into_val(env) + } + ArbitraryVal::U128(v) => v.into_val(env), + ArbitraryVal::I128(v) => v.into_val(env), + ArbitraryVal::Bytes(v) => { + let v: Bytes = v.into_val(env); + v.into_val(env) + } + ArbitraryVal::String(v) => { + let v: String = v.into_val(env); + v.into_val(env) + } + ArbitraryVal::Symbol(v) => { + let v: Symbol = v.into_val(env); + v.into_val(env) + } + ArbitraryVal::Vec(v) => v.into_val(env), + ArbitraryVal::Map(v) => v.into_val(env), + ArbitraryVal::Address(v) => { + let v: Address = v.into_val(env); + v.into_val(env) + } + }) + } + } + + #[derive(Arbitrary, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] + pub enum ArbitraryValVec { + Void( as SorobanArbitrary>::Prototype), + Bool( as SorobanArbitrary>::Prototype), + Error( as SorobanArbitrary>::Prototype), + U32( as SorobanArbitrary>::Prototype), + I32( as SorobanArbitrary>::Prototype), + U64( as SorobanArbitrary>::Prototype), + I64( as SorobanArbitrary>::Prototype), + U128( as SorobanArbitrary>::Prototype), + I128( as SorobanArbitrary>::Prototype), + U256( as SorobanArbitrary>::Prototype), + I256( as SorobanArbitrary>::Prototype), + Bytes( as SorobanArbitrary>::Prototype), + String( as SorobanArbitrary>::Prototype), + Symbol( as SorobanArbitrary>::Prototype), + Vec(> as SorobanArbitrary>::Prototype), + Map( as SorobanArbitrary>::Prototype), + Address( as SorobanArbitrary>::Prototype), + Val( as SorobanArbitrary>::Prototype), + } + + impl TryFromVal for Val { + type Error = ConversionError; + fn try_from_val(env: &Env, v: &ArbitraryValVec) -> Result { + Ok(match v { + ArbitraryValVec::Void(v) => { + let v: Vec<()> = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::Bool(v) => { + let v: Vec = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::Error(v) => { + let v: Vec = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::U32(v) => { + let v: Vec = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::I32(v) => { + let v: Vec = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::U64(v) => { + let v: Vec = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::I64(v) => { + let v: Vec = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::U128(v) => { + let v: Vec = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::I128(v) => { + let v: Vec = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::U256(v) => { + let v: Vec = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::I256(v) => { + let v: Vec = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::Bytes(v) => { + let v: Vec = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::String(v) => { + let v: Vec = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::Symbol(v) => { + let v: Vec = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::Vec(v) => { + let v: Vec> = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::Map(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::Address(v) => { + let v: Vec
= v.into_val(env); + v.into_val(env) + } + ArbitraryValVec::Val(v) => { + let v: Vec = v.into_val(env); + v.into_val(env) + } + }) + } + } + + #[derive(Arbitrary, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] + pub enum ArbitraryValMap { + VoidToVoid( as SorobanArbitrary>::Prototype), + BoolToBool( as SorobanArbitrary>::Prototype), + ErrorToError( as SorobanArbitrary>::Prototype), + U32ToU32( as SorobanArbitrary>::Prototype), + I32ToI32( as SorobanArbitrary>::Prototype), + U64ToU64( as SorobanArbitrary>::Prototype), + I64ToI64( as SorobanArbitrary>::Prototype), + U128ToU128( as SorobanArbitrary>::Prototype), + I128ToI128( as SorobanArbitrary>::Prototype), + U256ToU256( as SorobanArbitrary>::Prototype), + I256ToI256( as SorobanArbitrary>::Prototype), + BytesToBytes( as SorobanArbitrary>::Prototype), + StringToString( as SorobanArbitrary>::Prototype), + SymbolToSymbol( as SorobanArbitrary>::Prototype), + VecToVec(, Vec> as SorobanArbitrary>::Prototype), + MapToMap(, Map> as SorobanArbitrary>::Prototype), + AddressToAddress( as SorobanArbitrary>::Prototype), + ValToVal( as SorobanArbitrary>::Prototype), + } + + impl TryFromVal for Val { + type Error = ConversionError; + fn try_from_val(env: &Env, v: &ArbitraryValMap) -> Result { + Ok(match v { + ArbitraryValMap::VoidToVoid(v) => { + let v: Map<(), ()> = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::BoolToBool(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::ErrorToError(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::U32ToU32(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::I32ToI32(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::U64ToU64(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::I64ToI64(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::U128ToU128(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::I128ToI128(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::U256ToU256(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::I256ToI256(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::BytesToBytes(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::StringToString(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::SymbolToSymbol(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::VecToVec(v) => { + let v: Map, Vec> = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::MapToMap(v) => { + let v: Map, Map> = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::AddressToAddress(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + ArbitraryValMap::ValToVal(v) => { + let v: Map = v.into_val(env); + v.into_val(env) + } + }) + } + } +} + +/// Additional tools for writing fuzz tests. +mod fuzz_test_helpers { + use soroban_env_host::call_with_suppressed_panic_hook; + + /// Catch panics within a fuzz test. + /// + /// The `cargo-fuzz` test harness turns panics into test failures, + /// immediately aborting the process. + /// + /// This function catches panics while temporarily disabling the + /// `cargo-fuzz` panic handler. + /// + /// # Example + /// + /// ``` + /// # macro_rules! fuzz_target { + /// # (|$data:ident: $dty: ty| $body:block) => { }; + /// # } + /// # struct ExampleContract; + /// # impl ExampleContract { + /// # fn new(e: &soroban_sdk::Env, b: &soroban_sdk::BytesN<32>) { } + /// # fn deposit(&self, a: soroban_sdk::Address, n: i128) { } + /// # } + /// use soroban_sdk::{Address, Env}; + /// use soroban_sdk::arbitrary::{Arbitrary, SorobanArbitrary}; + /// + /// #[derive(Arbitrary, Debug)] + /// struct FuzzDeposit { + /// deposit_amount: i128, + /// deposit_address:
::Prototype, + /// } + /// + /// fuzz_target!(|input: FuzzDeposit| { + /// let env = Env::default(); + /// + /// let contract = ExampleContract::new(env, &env.register_contract(None, ExampleContract {})); + /// + /// let addresses: Address = input.deposit_address.into_val(&env); + /// let r = fuzz_catch_panic(|| { + /// contract.deposit(deposit_address, input.deposit_amount); + /// }); + /// }); + /// ``` + pub fn fuzz_catch_panic(f: F) -> std::thread::Result + where + F: FnOnce() -> R, + { + call_with_suppressed_panic_hook(std::panic::AssertUnwindSafe(f)) + } +} + +#[cfg(test)] +mod tests { + use crate::arbitrary::*; + use crate::{Bytes, BytesN, Map, String, Symbol, Val, Vec, I256, U256}; + use crate::{Env, IntoVal}; + use arbitrary::{Arbitrary, Unstructured}; + use rand::RngCore; + + fn run_test() + where + T: SorobanArbitrary, + T::Prototype: for<'a> Arbitrary<'a>, + { + let env = Env::default(); + let mut rng = rand::thread_rng(); + let mut rng_data = [0u8; 64]; + + for _ in 0..100 { + rng.fill_bytes(&mut rng_data); + let mut unstructured = Unstructured::new(&rng_data); + let input = T::Prototype::arbitrary(&mut unstructured).expect("SorobanArbitrary"); + let _val: T = input.into_val(&env); + } + } + + #[test] + fn test_u32() { + run_test::() + } + + #[test] + fn test_i32() { + run_test::() + } + + #[test] + fn test_u64() { + run_test::() + } + + #[test] + fn test_i64() { + run_test::() + } + + #[test] + fn test_u128() { + run_test::() + } + + #[test] + fn test_i128() { + run_test::() + } + + #[test] + fn test_u256() { + run_test::() + } + + #[test] + fn test_i256() { + run_test::() + } + + #[test] + fn test_bytes() { + run_test::() + } + + #[test] + fn test_string() { + run_test::() + } + + #[test] + fn test_symbol() { + run_test::() + } + + #[test] + fn test_bytes_n() { + run_test::>() + } + + #[test] + fn test_vec_u32() { + run_test::>() + } + + #[test] + fn test_vec_i32() { + run_test::>() + } + + #[test] + fn test_vec_bytes() { + run_test::>() + } + + #[test] + fn test_vec_bytes_n() { + run_test::>>() + } + + #[test] + fn test_vec_vec_bytes() { + run_test::>>() + } + + #[test] + fn test_map_u32() { + run_test::>>() + } + + #[test] + fn test_map_i32() { + run_test::>>() + } + + #[test] + fn test_map_bytes() { + run_test::>() + } + + #[test] + fn test_map_bytes_n() { + run_test::, Bytes>>() + } + + #[test] + fn test_map_vec() { + run_test::, Vec>>() + } + + #[test] + fn test_raw_val() { + run_test::() + } + + mod user_defined_types { + use crate as soroban_sdk; + use crate::arbitrary::tests::run_test; + use crate::{Address, Bytes, BytesN, Error, Map, Symbol, Vec, I256, U256}; + use soroban_sdk::contracttype; + + #[contracttype] + #[derive(Clone, Debug, Eq, PartialEq)] + struct PrivStruct { + count_u: u32, + count_i: i32, + bytes_n: BytesN<32>, + vec: Vec, + map: Map>, + u256: U256, + i156: I256, + error: Error, + address: Address, + symbol: Symbol, + } + + #[test] + fn test_user_defined_priv_struct() { + run_test::(); + } + + #[contracttype] + #[derive(Clone, Debug, Eq, PartialEq)] + struct PrivStructPubFields { + pub count_u: u32, + pub count_i: i32, + pub bytes_n: BytesN<32>, + pub vec: Vec, + pub map: Map>, + } + + #[test] + fn test_user_defined_priv_struct_pub_fields() { + run_test::(); + } + + #[contracttype] + #[derive(Clone, Debug, Eq, PartialEq)] + pub struct PubStruct { + count_u: u32, + count_i: i32, + bytes_n: BytesN<32>, + vec: Vec, + map: Map>, + } + + #[test] + fn test_user_defined_pub_struct() { + run_test::(); + } + + #[contracttype] + #[derive(Clone, Debug, Eq, PartialEq)] + pub struct PubStructPubFields { + pub count_u: u32, + pub count_i: i32, + pub bytes_n: BytesN<32>, + pub vec: Vec, + pub map: Map>, + } + + #[test] + fn test_user_defined_pubstruct_pub_fields() { + run_test::(); + } + + #[contracttype] + #[derive(Clone, Debug, Eq, PartialEq)] + struct PrivTupleStruct(u32, i32, BytesN<32>, Vec, Map>); + + #[test] + fn test_user_defined_priv_tuple_struct() { + run_test::(); + } + + #[contracttype] + #[derive(Clone, Debug, Eq, PartialEq)] + struct PrivTupleStructPubFields( + pub u32, + pub i32, + pub BytesN<32>, + pub Vec, + pub Map>, + ); + + #[test] + fn test_user_defined_priv_tuple_struct_pub_fields() { + run_test::(); + } + + #[contracttype] + #[derive(Clone, Debug, Eq, PartialEq)] + pub struct PubTupleStruct(u32, i32, BytesN<32>, Vec, Map>); + + #[test] + fn test_user_defined_pub_tuple_struct() { + run_test::(); + } + + #[contracttype] + #[derive(Clone, Debug, Eq, PartialEq)] + pub struct PubTupleStructPubFields( + pub u32, + pub i32, + pub BytesN<32>, + pub Vec, + pub Map>, + ); + + #[test] + fn test_user_defined_pub_tuple_struct_pub_fields() { + run_test::(); + } + + #[contracttype] + #[derive(Clone, Debug, Eq, PartialEq)] + pub(crate) struct PubCrateStruct(u32); + + #[test] + fn test_user_defined_pub_crate_struct() { + run_test::(); + } + + #[contracttype] + #[derive(Clone, Debug, Eq, PartialEq)] + enum PrivEnum { + A(u32), + Aa(u32, u32), + C, + D, + } + + #[test] + fn test_user_defined_priv_enum() { + run_test::(); + } + + #[contracttype] + #[derive(Clone, Debug, Eq, PartialEq)] + pub enum PubEnum { + A(u32), + C, + D, + } + + #[test] + fn test_user_defined_pub_enum() { + run_test::(); + } + + #[contracttype] + #[derive(Clone, Debug, Eq, PartialEq)] + pub(crate) enum PubCrateEnum { + A(u32), + C, + D, + } + + #[test] + fn test_user_defined_pub_crate_enum() { + run_test::(); + } + + #[contracttype] + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + enum PrivEnumInt { + A = 1, + C = 2, + D = 3, + } + + #[test] + fn test_user_defined_priv_enum_int() { + run_test::(); + } + + #[contracttype] + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum PubEnumInt { + A = 1, + C = 2, + D = 3, + } + + #[test] + fn test_user_defined_pub_enum_int() { + run_test::(); + } + + #[test] + fn test_declared_inside_a_fn() { + #[contracttype] + struct Foo { + a: u32, + } + + #[contracttype] + enum Bar { + Baz, + Qux, + } + + run_test::(); + run_test::(); + } + } +} diff --git a/soroban-sdk/src/arbitrary_extra.rs b/soroban-sdk/src/arbitrary_extra.rs new file mode 100644 index 000000000..9e05794dc --- /dev/null +++ b/soroban-sdk/src/arbitrary_extra.rs @@ -0,0 +1,71 @@ +//! Extra trait impls required by the bounds to `SorobanArbitrary`. +//! +//! These are in their own module so that they are defined even when "testutils" +//! is not configured, making type inference consistent between configurations. + +use crate::ConversionError; +use crate::Error; +use crate::{Env, TryFromVal}; + +impl TryFromVal for u32 { + type Error = ConversionError; + fn try_from_val(_env: &Env, v: &u32) -> Result { + Ok(*v) + } +} + +impl TryFromVal for i32 { + type Error = ConversionError; + fn try_from_val(_env: &Env, v: &i32) -> Result { + Ok(*v) + } +} + +impl TryFromVal for u64 { + type Error = ConversionError; + fn try_from_val(_env: &Env, v: &u64) -> Result { + Ok(*v) + } +} + +impl TryFromVal for i64 { + type Error = ConversionError; + fn try_from_val(_env: &Env, v: &i64) -> Result { + Ok(*v) + } +} + +impl TryFromVal for u128 { + type Error = ConversionError; + fn try_from_val(_env: &Env, v: &u128) -> Result { + Ok(*v) + } +} + +impl TryFromVal for i128 { + type Error = ConversionError; + fn try_from_val(_env: &Env, v: &i128) -> Result { + Ok(*v) + } +} + +impl TryFromVal for bool { + type Error = ConversionError; + fn try_from_val(_env: &Env, v: &bool) -> Result { + Ok(*v) + } +} + +impl TryFromVal for () { + type Error = ConversionError; + fn try_from_val(_env: &Env, v: &()) -> Result { + Ok(*v) + } +} + +impl TryFromVal for Error { + type Error = ConversionError; + fn try_from_val(_env: &Env, v: &Error) -> Result { + Ok(*v) + } +} diff --git a/soroban-sdk/src/lib.rs b/soroban-sdk/src/lib.rs index 88e1b70cc..294ca6968 100644 --- a/soroban-sdk/src/lib.rs +++ b/soroban-sdk/src/lib.rs @@ -686,4 +686,7 @@ pub mod xdr; pub mod testutils; +pub mod arbitrary; +mod arbitrary_extra; + mod tests; diff --git a/soroban-sdk/src/tests.rs b/soroban-sdk/src/tests.rs index 3c33ea635..2bcad8d75 100644 --- a/soroban-sdk/src/tests.rs +++ b/soroban-sdk/src/tests.rs @@ -18,5 +18,7 @@ mod contractimport; mod contractimport_with_error; mod contractimport_with_sha256; mod env; +mod proptest_scval_cmp; +mod proptest_val_cmp; mod token_client; mod token_spec; diff --git a/soroban-sdk/src/tests/proptest_scval_cmp.rs b/soroban-sdk/src/tests/proptest_scval_cmp.rs new file mode 100644 index 000000000..fb18ab391 --- /dev/null +++ b/soroban-sdk/src/tests/proptest_scval_cmp.rs @@ -0,0 +1,121 @@ +//! Check that Val and ScVal can be converted between each other, +//! and that their comparison functions are equivalent. + +use crate::xdr::ScVal; +use crate::Env; +use crate::TryFromVal; +use crate::Val; +use core::cmp::Ordering; +use proptest::prelude::*; +use proptest_arbitrary_interop::arb; +use soroban_env_host::Compare; + +proptest! { + #![proptest_config(ProptestConfig::with_cases(10000))] + + #[test] + fn test( + scval_1 in arb::(), + scval_2 in arb::(), + ) { + let env = &Env::default(); + + // Compare Ord & PartialOrd + let scval_cmp = Ord::cmp(&scval_1, &scval_2); + let scval_cmp_partial = PartialOrd::partial_cmp(&scval_1, &scval_2); + + prop_assert_eq!(Some(scval_cmp), scval_cmp_partial); + + let rawval_1 = Val::try_from_val(env, &scval_1); + let rawval_1 = match rawval_1 { + Ok(rawval_1) => rawval_1, + Err(_) => { + // Many ScVal's are invalid: + // + // - LedgerKeyNonce + // - Vec(None), Map(None) + // - Symbol with invalid chars + // - Map with duplicate keys + // - Containers with the above + return Ok(()); + } + }; + + let rawval_2 = Val::try_from_val(env, &scval_2); + let rawval_2 = match rawval_2 { + Ok(rawval_2) => rawval_2, + Err(_) => { + return Ok(()); + } + }; + + let rawval_cmp = env.compare(&rawval_1, &rawval_2).expect("cmp"); + + if scval_cmp != rawval_cmp { + panic!( + "scval and rawval don't compare the same:\n\ + {scval_1:#?}\n\ + {scval_2:#?}\n\ + {scval_cmp:#?}\n\ + {rawval_1:#?}\n\ + {rawval_2:#?}\n\ + {rawval_cmp:#?}" + ); + } + + // Compare Eq + let scval_partial_eq = PartialEq::eq(&scval_1, &scval_2); + let rawval_cmp_is_eq = scval_cmp == Ordering::Equal; + + prop_assert_eq!(scval_partial_eq, rawval_cmp_is_eq); + + // Compare for Budget + let budget = env.budget().0; + let scval_budget_cmp = budget.compare(&scval_1, &scval_2).expect("cmp"); + + if scval_budget_cmp != scval_cmp { + panic!( + "scval (budget) and scval don't compare the same:\n\ + {scval_1:#?}\n\ + {scval_2:#?}\n\ + {scval_budget_cmp:#?}\n\ + {scval_cmp:#?}" + ); + } + + // Roundtrip checks + { + let scval_after_1 = ScVal::try_from_val(env, &rawval_1); + let scval_after_1 = match scval_after_1 { + Ok(scval_after_1) => scval_after_1, + Err(e) => { + panic!( + "couldn't convert rawval to scval:\n\ + {rawval_1:?},\n\ + {scval_1:?},\n\ + {e:#?}" + ); + } + }; + + let scval_cmp_before_after_1 = Ord::cmp(&scval_1, &scval_after_1); + prop_assert_eq!(scval_cmp_before_after_1, Ordering::Equal); + + let scval_after_2 = ScVal::try_from_val(env, &rawval_2); + let scval_after_2 = match scval_after_2 { + Ok(scval_after_2) => scval_after_2, + Err(e) => { + panic!( + "couldn't convert rawval to scval:\n\ + {rawval_2:?},\n\ + {scval_2:?},\n\ + {e:#?}" + ); + } + }; + + let scval_cmp_before_after_2 = Ord::cmp(&scval_2, &scval_after_2); + prop_assert_eq!(scval_cmp_before_after_2, Ordering::Equal); + } + } +} diff --git a/soroban-sdk/src/tests/proptest_val_cmp.rs b/soroban-sdk/src/tests/proptest_val_cmp.rs new file mode 100644 index 000000000..df5ffc1bd --- /dev/null +++ b/soroban-sdk/src/tests/proptest_val_cmp.rs @@ -0,0 +1,135 @@ +//! Check that Val and ScVal can be converted between each other, +//! and that their comparison functions are equivalent. + +use crate::arbitrary::SorobanArbitrary; +use crate::xdr::ScVal; +use crate::Env; +use crate::Val; +use crate::{FromVal, TryFromVal}; +use core::cmp::Ordering; +use proptest::prelude::*; +use proptest_arbitrary_interop::arb; +use soroban_env_host::Compare; + +proptest! { + #![proptest_config(ProptestConfig::with_cases(10000))] + + #[test] + fn test( + rawval_proto_1 in arb::<::Prototype>(), + rawval_proto_2 in arb::<::Prototype>(), + ) { + let env = &Env::default(); + let budget = env.budget().0; + + let rawval_1 = Val::from_val(env, &rawval_proto_1); + let rawval_2 = Val::from_val(env, &rawval_proto_2); + + let (scval_1, scval_2) = { + let scval_1 = ScVal::try_from_val(env, &rawval_1); + let scval_2 = ScVal::try_from_val(env, &rawval_2); + + let scval_1 = match scval_1 { + Ok(scval_1) => scval_1, + Err(e) => { + panic!( + "couldn't convert rawval to scval:\n\ + {rawval_1:?},\n\ + {e:#?}" + ); + } + }; + + let scval_2 = match scval_2 { + Ok(scval_2) => scval_2, + Err(e) => { + panic!( + "couldn't convert rawval to scval:\n\ + {rawval_2:?},\n\ + {e:#?}" + ); + } + }; + + (scval_1, scval_2) + }; + + // Check the comparison functions + { + let rawval_cmp = env.compare(&rawval_1, &rawval_2); + let rawval_cmp = rawval_cmp.expect("cmp"); + let scval_cmp = Ord::cmp(&scval_1, &scval_2); + + let rawval_cmp_is_eq = rawval_cmp == Ordering::Equal; + + if rawval_cmp != scval_cmp { + panic!( + "rawval and scval don't compare the same:\n\ + {rawval_1:#?}\n\ + {rawval_2:#?}\n\ + {rawval_cmp:#?}\n\ + {scval_1:#?}\n\ + {scval_2:#?}\n\ + {scval_cmp:#?}" + ); + } + + let scval_cmp_partial = PartialOrd::partial_cmp(&scval_1, &scval_2); + + prop_assert_eq!(Some(scval_cmp), scval_cmp_partial); + + let scval_partial_eq = PartialEq::eq(&scval_1, &scval_2); + prop_assert_eq!(rawval_cmp_is_eq, scval_partial_eq); + + // Compare for Budget + let scval_budget_cmp = budget.compare(&scval_1, &scval_2); + let scval_budget_cmp = scval_budget_cmp.expect("cmp"); + if rawval_cmp != scval_budget_cmp { + panic!( + "rawval and scval (budget) don't compare the same:\n\ + {rawval_1:#?}\n\ + {rawval_2:#?}\n\ + {rawval_cmp:#?}\n\ + {scval_1:#?}\n\ + {scval_2:#?}\n\ + {scval_budget_cmp:#?}" + ); + } + } + + // Roundtrip checks + { + let rawval_after_1 = Val::try_from_val(env, &scval_1); + let rawval_after_1 = match rawval_after_1 { + Ok(rawval_after_1) => rawval_after_1, + Err(e) => { + panic!( + "couldn't convert scval to rawval:\n\ + {scval_1:?},\n\ + {e:#?}" + ); + } + }; + + let rawval_cmp_before_after_1 = env.compare(&rawval_1, &rawval_after_1).expect("compare"); + + prop_assert_eq!(rawval_cmp_before_after_1, Ordering::Equal); + + let rawval_after_2 = Val::try_from_val(env, &scval_2); + let rawval_after_2 = match rawval_after_2 { + Ok(rawval_after_2) => rawval_after_2, + Err(e) => { + panic!( + "couldn't convert scval to rawval:\n\ + {scval_2:?},\n\ + {e:#?}" + ); + } + }; + + let rawval_cmp_before_after_2 = env.compare(&rawval_2, &rawval_after_2).expect("compare"); + + prop_assert_eq!(rawval_cmp_before_after_2, Ordering::Equal); + } + } +} diff --git a/soroban-sdk/src/testutils.rs b/soroban-sdk/src/testutils.rs index 15d4bd3cd..13f84c983 100644 --- a/soroban-sdk/src/testutils.rs +++ b/soroban-sdk/src/testutils.rs @@ -68,7 +68,7 @@ pub mod budget { /// # #[cfg(not(feature = "testutils"))] /// # fn main() { } /// ``` - pub struct Budget(crate::env::internal::budget::Budget); + pub struct Budget(pub(crate) crate::env::internal::budget::Budget); impl Display for Budget { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { diff --git a/tests/fuzz/Cargo.toml b/tests/fuzz/Cargo.toml new file mode 100644 index 000000000..8184f09c7 --- /dev/null +++ b/tests/fuzz/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "test_fuzz" +version.workspace = true +authors = ["Stellar Development Foundation "] +license = "Apache-2.0" +edition = "2021" +publish = false +rust-version = "1.70" + +[lib] +# Adding rlib is required so that the test_fuzz crate can be imported as a +# library into the fuzzing crate. However, having multiple crate types is a +# problem, it'll cause LTO optimizations to be disabled for the cdylib build. +# TODO: Figure out what to do about this. +crate-type = ["cdylib", "rlib"] +doctest = false + +[features] +testutils = ["soroban-sdk/testutils"] + +[dependencies] +soroban-sdk = {path = "../../soroban-sdk"} + +[dev-dependencies] +soroban-sdk = {path = "../../soroban-sdk", features = ["testutils"]} diff --git a/tests/fuzz/fuzz/.gitignore b/tests/fuzz/fuzz/.gitignore new file mode 100644 index 000000000..1a45eee77 --- /dev/null +++ b/tests/fuzz/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/tests/fuzz/fuzz/Cargo.lock b/tests/fuzz/fuzz/Cargo.lock new file mode 100644 index 000000000..ac8000c97 --- /dev/null +++ b/tests/fuzz/fuzz/Cargo.lock @@ -0,0 +1,1460 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes-lit" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0adabf37211a5276e46335feabcbb1530c95eb3fdf85f324c7db942770aa025d" +dependencies = [ + "num-bigint", + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "winapi", +] + +[[package]] +name = "const-oid" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" +dependencies = [ + "libc", +] + +[[package]] +name = "crate-git-revision" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f998aef136a4e7833b0e4f0fc0939a59c40140b28e0ffbf524ad84fb2cc568c8" +dependencies = [ + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "darling" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.18", +] + +[[package]] +name = "darling_macro" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "der" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "ecdsa" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature 2.1.0", + "spki", +] + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature 1.6.4", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ethnum" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0198b9d0078e0f30dedc7acbb21c974e838fc8fae3ee170128658a98cb2c1c04" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + +[[package]] +name = "intx" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f38a50a899dc47a6d0ed5508e7f601a2e34c3a85303514b5d137f3c10a0c75" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.7", + "signature 2.1.0", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "libc" +version = "0.2.146" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beb09950ae85a0a94b27676cccf37da5ff13f27076aa1adbc6545dd0d0e1bd4e" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.30.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b0377b720bde721213a46cda1289b2f34abf0a436907cad91578c20de0454d" +dependencies = [ + "proc-macro2", + "syn 2.0.18", +] + +[[package]] +name = "proc-macro2" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "sec1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "serde" +version = "1.0.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "serde_json" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" +dependencies = [ + "base64 0.21.2", + "chrono", + "hex", + "indexmap", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "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 = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "soroban-env-common" +version = "0.0.16" +source = "git+https://github.com/stellar/rs-soroban-env?rev=c43bbd47959dde2e39eeeb5b7207868a44e96c7d#c43bbd47959dde2e39eeeb5b7207868a44e96c7d" +dependencies = [ + "arbitrary", + "crate-git-revision", + "ethnum", + "serde", + "soroban-env-macros", + "soroban-wasmi", + "static_assertions", + "stellar-xdr", +] + +[[package]] +name = "soroban-env-guest" +version = "0.0.16" +source = "git+https://github.com/stellar/rs-soroban-env?rev=c43bbd47959dde2e39eeeb5b7207868a44e96c7d#c43bbd47959dde2e39eeeb5b7207868a44e96c7d" +dependencies = [ + "soroban-env-common", + "static_assertions", +] + +[[package]] +name = "soroban-env-host" +version = "0.0.16" +source = "git+https://github.com/stellar/rs-soroban-env?rev=c43bbd47959dde2e39eeeb5b7207868a44e96c7d#c43bbd47959dde2e39eeeb5b7207868a44e96c7d" +dependencies = [ + "backtrace", + "curve25519-dalek", + "ed25519-dalek", + "getrandom 0.2.10", + "hex", + "k256", + "log", + "num-derive", + "num-integer", + "num-traits", + "rand", + "rand_chacha", + "sha2 0.9.9", + "sha3", + "soroban-env-common", + "soroban-native-sdk-macros", + "soroban-wasmi", + "static_assertions", + "stellar-strkey", +] + +[[package]] +name = "soroban-env-macros" +version = "0.0.16" +source = "git+https://github.com/stellar/rs-soroban-env?rev=c43bbd47959dde2e39eeeb5b7207868a44e96c7d#c43bbd47959dde2e39eeeb5b7207868a44e96c7d" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "serde", + "serde_json", + "stellar-xdr", + "syn 2.0.18", + "thiserror", +] + +[[package]] +name = "soroban-ledger-snapshot" +version = "0.8.4" +dependencies = [ + "serde", + "serde_json", + "soroban-env-common", + "soroban-env-host", + "thiserror", +] + +[[package]] +name = "soroban-native-sdk-macros" +version = "0.0.16" +source = "git+https://github.com/stellar/rs-soroban-env?rev=c43bbd47959dde2e39eeeb5b7207868a44e96c7d#c43bbd47959dde2e39eeeb5b7207868a44e96c7d" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "soroban-sdk" +version = "0.8.4" +dependencies = [ + "arbitrary", + "bytes-lit", + "ed25519-dalek", + "rand", + "soroban-env-guest", + "soroban-env-host", + "soroban-ledger-snapshot", + "soroban-sdk-macros", + "stellar-strkey", +] + +[[package]] +name = "soroban-sdk-macros" +version = "0.8.4" +dependencies = [ + "darling", + "itertools", + "proc-macro2", + "quote", + "sha2 0.9.9", + "soroban-env-common", + "soroban-spec", + "soroban-spec-rust", + "stellar-xdr", + "syn 2.0.18", +] + +[[package]] +name = "soroban-spec" +version = "0.8.4" +dependencies = [ + "base64 0.13.1", + "stellar-xdr", + "thiserror", + "wasmparser", +] + +[[package]] +name = "soroban-spec-rust" +version = "0.8.4" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "sha2 0.9.9", + "soroban-spec", + "stellar-xdr", + "syn 2.0.18", + "thiserror", +] + +[[package]] +name = "soroban-wasmi" +version = "0.30.0-soroban" +source = "git+https://github.com/stellar/wasmi?rev=1a2bc7f#1a2bc7f3801c565c2dab22021255a164c05a7f76" +dependencies = [ + "intx", + "smallvec", + "soroban-wasmi_core", + "spin", + "wasmi_arena", + "wasmparser-nostd", +] + +[[package]] +name = "soroban-wasmi_core" +version = "0.30.0-soroban" +source = "git+https://github.com/stellar/wasmi?rev=1a2bc7f#1a2bc7f3801c565c2dab22021255a164c05a7f76" +dependencies = [ + "downcast-rs", + "libm", + "num-traits", + "paste", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stellar-strkey" +version = "0.0.7" +source = "git+https://github.com/stellar/rs-stellar-strkey?rev=e6ba45c60c16de28c7522586b80ed0150157df73#e6ba45c60c16de28c7522586b80ed0150157df73" +dependencies = [ + "base32", + "thiserror", +] + +[[package]] +name = "stellar-xdr" +version = "0.0.16" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=50c7a57e55603bc57719b1d096091b3239ea6859#50c7a57e55603bc57719b1d096091b3239ea6859" +dependencies = [ + "arbitrary", + "base64 0.13.1", + "crate-git-revision", + "hex", + "serde", + "serde_with", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "test_fuzz" +version = "0.8.4" +dependencies = [ + "soroban-sdk", +] + +[[package]] +name = "test_fuzz-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "soroban-sdk", + "test_fuzz", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "time" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +dependencies = [ + "time-core", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.18", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wasmi_arena" +version = "0.4.0" +source = "git+https://github.com/stellar/wasmi?rev=1a2bc7f#1a2bc7f3801c565c2dab22021255a164c05a7f76" + +[[package]] +name = "wasmparser" +version = "0.88.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8cf7dd82407fe68161bedcd57fde15596f32ebf6e9b3bdbf3ae1da20e38e5e" +dependencies = [ + "indexmap", +] + +[[package]] +name = "wasmparser-nostd" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9157cab83003221bfd385833ab587a039f5d6fa7304854042ba358a3b09e0724" +dependencies = [ + "indexmap-nostd", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] diff --git a/tests/fuzz/fuzz/Cargo.toml b/tests/fuzz/fuzz/Cargo.toml new file mode 100644 index 000000000..ab7af11e3 --- /dev/null +++ b/tests/fuzz/fuzz/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "test_fuzz-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +soroban-sdk = { path = "../../../soroban-sdk", features = [ "testutils" ]} +test_fuzz = { path = "..", features = [ "testutils" ] } + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "fuzz_target_1" +path = "fuzz_targets/fuzz_target_1.rs" +test = false +doc = false diff --git a/tests/fuzz/fuzz/fuzz_targets/fuzz_target_1.rs b/tests/fuzz/fuzz/fuzz_targets/fuzz_target_1.rs new file mode 100644 index 000000000..617b4463d --- /dev/null +++ b/tests/fuzz/fuzz/fuzz_targets/fuzz_target_1.rs @@ -0,0 +1,25 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; + +use soroban_sdk::{arbitrary::{arbitrary,Arbitrary,SorobanArbitrary}, Env, IntoVal, U256}; + +use test_fuzz::{Contract, ContractClient}; + +#[derive(Arbitrary, Debug)] +struct Input { + a: ::Prototype, + b: ::Prototype, +} + +fuzz_target!(|input: Input| { + let env = Env::default(); + + let a: U256 = input.a.into_val(&env); + let b: U256 = input.b.into_val(&env); + + let contract_id = env.register_contract(None, Contract); + let client = ContractClient::new(&env, &contract_id); + + let _ = client.run(&a, &b); +}); diff --git a/tests/fuzz/src/lib.rs b/tests/fuzz/src/lib.rs new file mode 100644 index 000000000..fa6c3a6df --- /dev/null +++ b/tests/fuzz/src/lib.rs @@ -0,0 +1,13 @@ +#![no_std] +use soroban_sdk::{contractimpl, U256}; + +pub struct Contract; + +#[contractimpl] +impl Contract { + pub fn run(a: U256, b: U256) { + if a < b { + panic!("unexpected") + } + } +}