From 9af9725f4de03cc2727ecb64f31d8536bb8aef61 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Thu, 26 Dec 2024 17:54:07 +0700 Subject: [PATCH] restore libwasmvm to ease reference --- ibc_test.go | 14 +- internal/api/mocks.go | 4 +- internal/api/testdb/memdb_iterator.go | 6 + internal/runtime/hostfunctions.go | 35 +- libwasmvm/.gitignore | 3 + libwasmvm/Cargo.lock | 2489 ++++++++++++++++++++++++ libwasmvm/Cargo.toml | 63 + libwasmvm/artifacts/.gitkeep | 0 libwasmvm/bindings.h | 645 ++++++ libwasmvm/build.rs | 9 + libwasmvm/cbindgen.toml | 146 ++ libwasmvm/clippy.toml | 1 + libwasmvm/src/api.rs | 159 ++ libwasmvm/src/args.rs | 9 + libwasmvm/src/cache.rs | 1078 ++++++++++ libwasmvm/src/calls.rs | 680 +++++++ libwasmvm/src/db.rs | 70 + libwasmvm/src/error/go.rs | 191 ++ libwasmvm/src/error/mod.rs | 7 + libwasmvm/src/error/rust.rs | 585 ++++++ libwasmvm/src/examples/wasmvmstatic.rs | 5 + libwasmvm/src/gas_meter.rs | 5 + libwasmvm/src/gas_report.rs | 31 + libwasmvm/src/handle_vm_panic.rs | 34 + libwasmvm/src/iterator.rs | 203 ++ libwasmvm/src/lib.rs | 36 + libwasmvm/src/memory.rs | 470 +++++ libwasmvm/src/querier.rs | 92 + libwasmvm/src/storage.rs | 211 ++ libwasmvm/src/test_utils.rs | 100 + libwasmvm/src/tests.rs | 83 + libwasmvm/src/version.rs | 56 + libwasmvm/src/vtables.rs | 46 + 33 files changed, 7557 insertions(+), 9 deletions(-) create mode 100644 libwasmvm/.gitignore create mode 100644 libwasmvm/Cargo.lock create mode 100644 libwasmvm/Cargo.toml create mode 100644 libwasmvm/artifacts/.gitkeep create mode 100644 libwasmvm/bindings.h create mode 100644 libwasmvm/build.rs create mode 100644 libwasmvm/cbindgen.toml create mode 100644 libwasmvm/clippy.toml create mode 100644 libwasmvm/src/api.rs create mode 100644 libwasmvm/src/args.rs create mode 100644 libwasmvm/src/cache.rs create mode 100644 libwasmvm/src/calls.rs create mode 100644 libwasmvm/src/db.rs create mode 100644 libwasmvm/src/error/go.rs create mode 100644 libwasmvm/src/error/mod.rs create mode 100644 libwasmvm/src/error/rust.rs create mode 100644 libwasmvm/src/examples/wasmvmstatic.rs create mode 100644 libwasmvm/src/gas_meter.rs create mode 100644 libwasmvm/src/gas_report.rs create mode 100644 libwasmvm/src/handle_vm_panic.rs create mode 100644 libwasmvm/src/iterator.rs create mode 100644 libwasmvm/src/lib.rs create mode 100644 libwasmvm/src/memory.rs create mode 100644 libwasmvm/src/querier.rs create mode 100644 libwasmvm/src/storage.rs create mode 100644 libwasmvm/src/test_utils.rs create mode 100644 libwasmvm/src/tests.rs create mode 100644 libwasmvm/src/version.rs create mode 100644 libwasmvm/src/vtables.rs diff --git a/ibc_test.go b/ibc_test.go index bfdcc0503..2da754d14 100644 --- a/ibc_test.go +++ b/ibc_test.go @@ -1,3 +1,5 @@ +//go:build cgo && !nolink_libwasmvm + package cosmwasm import ( @@ -74,7 +76,6 @@ type AcknowledgeDispatch struct { } func toBytes(t *testing.T, v interface{}) []byte { - t.Helper() bz, err := json.Marshal(v) require.NoError(t, err) return bz @@ -106,10 +107,9 @@ func TestIBCHandshake(t *testing.T) { } i, _, err := vm.Instantiate(checksum, env, info, toBytes(t, init_msg), store, *goapi, querier, gasMeter1, TESTING_GAS_LIMIT, deserCost) require.NoError(t, err) - t.Logf("Instantiation response: %+v", i) assert.NotNil(t, i.Ok) iResponse := i.Ok - require.Empty(t, iResponse.Messages) + require.Equal(t, 0, len(iResponse.Messages)) // channel open gasMeter2 := api.NewMockGasMeter(TESTING_GAS_LIMIT) @@ -132,7 +132,7 @@ func TestIBCHandshake(t *testing.T) { require.NoError(t, err) require.NotNil(t, conn.Ok) connResponse := conn.Ok - require.Len(t, connResponse.Messages, 1) + require.Equal(t, 1, len(connResponse.Messages)) // check for the expected custom event expected_events := []types.Event{{ @@ -200,7 +200,7 @@ func TestIBCPacketDispatch(t *testing.T) { require.NoError(t, err) require.NotNil(t, conn.Ok) connResponse := conn.Ok - require.Len(t, connResponse.Messages, 1) + require.Equal(t, 1, len(connResponse.Messages)) id := connResponse.Messages[0].ID // mock reflect init callback (to store address) @@ -237,7 +237,7 @@ func TestIBCPacketDispatch(t *testing.T) { var accounts ListAccountsResponse err = json.Unmarshal(qResponse, &accounts) require.NoError(t, err) - require.Len(t, accounts.Accounts, 1) + require.Equal(t, 1, len(accounts.Accounts)) require.Equal(t, CHANNEL_ID, accounts.Accounts[0].ChannelID) require.Equal(t, REFLECT_ADDR, accounts.Accounts[0].Account) @@ -332,7 +332,7 @@ func TestIBCMsgGetChannel(t *testing.T) { require.Equal(t, msg1.GetChannel(), msg4.GetChannel()) require.Equal(t, msg1.GetChannel(), msg5.GetChannel()) require.Equal(t, msg1.GetChannel(), msg6.GetChannel()) - require.Equal(t, CHANNEL_ID, msg1.GetChannel().Endpoint.ChannelID) + require.Equal(t, msg1.GetChannel().Endpoint.ChannelID, CHANNEL_ID) } func TestIBCMsgGetCounterVersion(t *testing.T) { diff --git a/internal/api/mocks.go b/internal/api/mocks.go index c33ee06d0..3ec68bf17 100644 --- a/internal/api/mocks.go +++ b/internal/api/mocks.go @@ -19,11 +19,13 @@ import ( const MOCK_CONTRACT_ADDR = "contract" +// MockEnv returns a mock environment for testing +// this is the original, and should not be changed. func MockEnv() types.Env { return types.Env{ Block: types.BlockInfo{ Height: 123, - Time: types.Uint64(1578939743_987654321), + Time: 1578939743_987654321, ChainID: "foobar", }, Transaction: &types.TransactionInfo{ diff --git a/internal/api/testdb/memdb_iterator.go b/internal/api/testdb/memdb_iterator.go index a65efa281..7d4d8a90e 100644 --- a/internal/api/testdb/memdb_iterator.go +++ b/internal/api/testdb/memdb_iterator.go @@ -141,12 +141,18 @@ func (i *memDBIterator) Error() error { // Key implements Iterator. func (i *memDBIterator) Key() []byte { i.assertIsValid() + if i.item.key == nil || len(i.item.key) == 0 { + return nil + } return i.item.key } // Value implements Iterator. func (i *memDBIterator) Value() []byte { i.assertIsValid() + if i.item.value == nil || len(i.item.value) == 0 { + return nil + } return i.item.value } diff --git a/internal/runtime/hostfunctions.go b/internal/runtime/hostfunctions.go index 6596b8a6f..69efd38e2 100644 --- a/internal/runtime/hostfunctions.go +++ b/internal/runtime/hostfunctions.go @@ -431,7 +431,40 @@ func hostCloseIterator(ctx context.Context, mod api.Module, callID, iterID uint6 // hostAbort implements the abort function required by Wasm modules func hostAbort(ctx context.Context, mod api.Module, code uint32) { - panic(fmt.Sprintf("Wasm contract aborted with code: %d", code)) + // Print debug information about the abort + fmt.Printf("Debug: Wasm contract abort triggered\n") + fmt.Printf("Debug: Abort code: %d (0x%x)\n", code, code) + + // Try to get any memory exports to check for error messages + if mem := mod.Memory(); mem != nil { + // Try to read memory around the abort code location + // We'll read a few different ranges to try to catch any error message + ranges := []struct{ start, size uint32 }{ + {code - 100, 200}, // Around the code point + {0, 256}, // Start of memory + {code & 0xFFFF, 256}, // Lower 16 bits as offset + } + + for _, r := range ranges { + if data, ok := mem.Read(r.start, r.size); ok { + if len(data) > 0 { + // Try to interpret the memory as both string and raw bytes + fmt.Printf("Debug: Memory at offset %d:\n", r.start) + fmt.Printf(" As string: %s\n", string(data)) + fmt.Printf(" As bytes: %v\n", data) + } + } + } + } + + env := ctx.Value(envKey).(*RuntimeEnvironment) + if env != nil { + fmt.Printf("Debug: Runtime environment state:\n") + fmt.Printf(" Gas used: %d\n", env.GasUsed) + fmt.Printf(" Gas limit: %d\n", env.Gas.GasConsumed()) + } + + panic(fmt.Sprintf("Wasm contract aborted with code: %d (0x%x)", code, code)) } // hostDbRead implements db_read diff --git a/libwasmvm/.gitignore b/libwasmvm/.gitignore new file mode 100644 index 000000000..1f2eb5641 --- /dev/null +++ b/libwasmvm/.gitignore @@ -0,0 +1,3 @@ +# Build results +target/ +artifacts/ diff --git a/libwasmvm/Cargo.lock b/libwasmvm/Cargo.lock new file mode 100644 index 000000000..4cb79f72a --- /dev/null +++ b/libwasmvm/Cargo.lock @@ -0,0 +1,2489 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli 0.28.1", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools", + "num-traits", + "rayon", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rayon", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", + "rayon", +] + +[[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.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +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 = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[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 = "bnum" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e31ea183f6ee62ac8b8a8cf7feddd766317adfb13ff469de57ce033efd6a790" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytecheck" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cbindgen" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" +dependencies = [ + "clap", + "heck 0.4.1", + "indexmap 2.2.6", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.79", + "tempfile", + "toml", +] + +[[package]] +name = "cc" +version = "1.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "clru" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "corosensei" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80128832c58ea9cbd041d2a759ec449224487b2c1e400453d99d244eead87a8e" +dependencies = [ + "autocfg", + "cfg-if", + "libc", + "scopeguard", + "windows-sys 0.33.0", +] + +[[package]] +name = "cosmwasm-core" +version = "2.2.0" +source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.2.0#b9a149fde2a787b7221918b0651532ca2668d761" + +[[package]] +name = "cosmwasm-crypto" +version = "2.2.0" +source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.2.0#b9a149fde2a787b7221918b0651532ca2668d761" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-serialize", + "cosmwasm-core", + "curve25519-dalek", + "digest", + "ecdsa", + "ed25519-zebra", + "k256", + "num-traits", + "p256", + "rand_core", + "rayon", + "sha2", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "2.2.0" +source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.2.0#b9a149fde2a787b7221918b0651532ca2668d761" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "cosmwasm-std" +version = "2.2.0" +source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.2.0#b9a149fde2a787b7221918b0651532ca2668d761" +dependencies = [ + "base64", + "bech32", + "bnum", + "cosmwasm-core", + "cosmwasm-crypto", + "cosmwasm-derive", + "derive_more", + "hex", + "rand_core", + "rmp-serde", + "schemars", + "serde", + "serde-json-wasm", + "sha2", + "static_assertions", + "thiserror", +] + +[[package]] +name = "cosmwasm-vm" +version = "2.2.0" +source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.2.0#b9a149fde2a787b7221918b0651532ca2668d761" +dependencies = [ + "bech32", + "blake2", + "bytes", + "clru", + "cosmwasm-core", + "cosmwasm-crypto", + "cosmwasm-std", + "cosmwasm-vm-derive", + "crc32fast", + "derivative", + "hex", + "rand_core", + "schemars", + "serde", + "serde_json", + "sha2", + "strum", + "thiserror", + "tracing", + "wasmer", + "wasmer-middlewares", + "wasmer-types", +] + +[[package]] +name = "cosmwasm-vm-derive" +version = "2.2.0" +source = "git+https://github.com/CosmWasm/cosmwasm.git?rev=v2.2.0#b9a149fde2a787b7221918b0651532ca2668d761" +dependencies = [ + "blake2", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "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 = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "1.0.0-beta.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7abbfc297053be59290e3152f8cbcd52c8642e0728b69ee187d991d4c1af08d" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0-beta.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dyn-clone" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" + +[[package]] +name = "dynasm" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dynasmrt" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" +dependencies = [ + "byteorder", + "dynasm", + "memmap2 0.5.10", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "hashbrown 0.14.3", + "hex", + "rand_core", + "sha2", + "zeroize", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-iterator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enumset" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[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.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +dependencies = [ + "fallible-iterator", + "indexmap 1.9.3", + "stable_deref_trait", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.11", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[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 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[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.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d28bba84adfe6646737845bc5ebbfa2c08424eb1c37e94a1fd2a82adb56a872" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "more-asserts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" + +[[package]] +name = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "region" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" +dependencies = [ + "bitflags 1.3.2", + "libc", + "mach2", + "windows-sys 0.52.0", +] + +[[package]] +name = "rend" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rkyv" +version = "0.7.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527a97cdfef66f65998b5f3b637c26f5a5ec09cc52a3f9932313ac645f4190f5" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "indexmap 1.9.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5c462a1328c8e67e4d6dbad1eb0355dd43e8ab432c6e227a43657f16ade5033" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "schemars" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "self_cell" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05da0d153dd4595bdffd5099dc0e9ce425b205ee648eb93437ff7302af8c9a5" +dependencies = [ + "serde", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_json" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shared-buffer" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6c99835bad52957e7aa241d3975ed17c1e5f8c92026377d117a606f36b84b16" +dependencies = [ + "bytes", + "memmap2 0.6.2", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7993a8e3a9e88a00351486baae9522c91b123a088f76469e5bd5cc17198ea87" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.79", +] + +[[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.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" + +[[package]] +name = "tempfile" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.8.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" +dependencies = [ + "indexmap 2.2.6", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" + +[[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.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.79", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" + +[[package]] +name = "wasmer" +version = "4.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b28d4251f96ece14460328c56ee0525edcf4bbb08748cfd87fef3580ae4d403" +dependencies = [ + "bytes", + "cfg-if", + "derivative", + "indexmap 1.9.3", + "js-sys", + "more-asserts", + "rustc-demangle", + "serde", + "serde-wasm-bindgen", + "shared-buffer", + "target-lexicon", + "thiserror", + "tracing", + "wasm-bindgen", + "wasmer-compiler", + "wasmer-compiler-singlepass", + "wasmer-derive", + "wasmer-types", + "wasmer-vm", + "windows-sys 0.59.0", +] + +[[package]] +name = "wasmer-compiler" +version = "4.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "009b8417d51dbca8ac9a640ea999cc924fc59040a81245ecd0e092cb7c45dc10" +dependencies = [ + "backtrace", + "bytes", + "cfg-if", + "enum-iterator", + "enumset", + "lazy_static", + "leb128", + "libc", + "memmap2 0.5.10", + "more-asserts", + "region", + "rkyv", + "self_cell", + "shared-buffer", + "smallvec", + "thiserror", + "wasmer-types", + "wasmer-vm", + "wasmparser", + "windows-sys 0.59.0", + "xxhash-rust", +] + +[[package]] +name = "wasmer-compiler-singlepass" +version = "4.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6add6b3abdbd2db38dd6a42e4727d860b893e5a6ba3ac49bdd42fe0e6dc06db" +dependencies = [ + "byteorder", + "dynasm", + "dynasmrt", + "enumset", + "gimli 0.26.2", + "lazy_static", + "more-asserts", + "rayon", + "smallvec", + "wasmer-compiler", + "wasmer-types", +] + +[[package]] +name = "wasmer-derive" +version = "4.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02592d86ac19fb09c972e72edeb3e57ac5c569eac7e77b919b165da014e8c139" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "wasmer-middlewares" +version = "4.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b8606706b694465035cbdd85a5a1ea437b7cd851e6a8dfe4e387a3e8f81ef78" +dependencies = [ + "wasmer", + "wasmer-types", + "wasmer-vm", +] + +[[package]] +name = "wasmer-types" +version = "4.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d22a00f1a90e9e66d5427853f41e76d8ab89e03eb3034debd11933607fef56a" +dependencies = [ + "bytecheck", + "enum-iterator", + "enumset", + "getrandom", + "hex", + "indexmap 1.9.3", + "more-asserts", + "rkyv", + "sha2", + "target-lexicon", + "thiserror", + "xxhash-rust", +] + +[[package]] +name = "wasmer-vm" +version = "4.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87d88e8355157cd730fb81e33c3b4d6849fd44c26d32bf78820638e1d935967b" +dependencies = [ + "backtrace", + "cc", + "cfg-if", + "corosensei", + "crossbeam-queue", + "dashmap", + "derivative", + "enum-iterator", + "fnv", + "indexmap 1.9.3", + "lazy_static", + "libc", + "mach2", + "memoffset", + "more-asserts", + "region", + "scopeguard", + "thiserror", + "wasmer-types", + "windows-sys 0.59.0", +] + +[[package]] +name = "wasmparser" +version = "0.121.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab" +dependencies = [ + "bitflags 2.4.2", + "indexmap 2.2.6", + "semver", +] + +[[package]] +name = "wasmvm" +version = "2.2.1" +dependencies = [ + "cbindgen", + "cosmwasm-std", + "cosmwasm-vm", + "errno", + "hex", + "rmp-serde", + "serde", + "serde_json", + "tempfile", + "thiserror", + "time", +] + +[[package]] +name = "windows-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" +dependencies = [ + "windows_aarch64_msvc 0.33.0", + "windows_i686_gnu 0.33.0", + "windows_i686_msvc 0.33.0", + "windows_x86_64_gnu 0.33.0", + "windows_x86_64_msvc 0.33.0", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927da81e25be1e1a2901d59b81b37dd2efd1fc9c9345a55007f09bf5a2d3ee03" + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +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.79", +] diff --git a/libwasmvm/Cargo.toml b/libwasmvm/Cargo.toml new file mode 100644 index 000000000..908942f28 --- /dev/null +++ b/libwasmvm/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "wasmvm" +version = "2.2.1" +publish = false +authors = ["Ethan Frey "] +edition = "2021" +description = "Go bindings for cosmwasm contracts" +repository = "https://github.com/CosmWasm/wasmvm" +license = "Apache-2.0" +readme = "README.md" +exclude = [".circleci/*", ".gitignore"] + +[lib] +crate-type = ["cdylib", "rlib"] + +# the example is to allow us to compile a muslc static lib with the same codebase as we compile the +# normal dynamic libs (best workaround I could find to override crate-type on the command line) +[[example]] +name = "wasmvmstatic" +path = "src/examples/wasmvmstatic.rs" +crate-type = ["staticlib"] + +[features] +default = [] +# This feature requires Rust nightly because it depends on the unstable backtrace feature. +backtraces = [] + +[dependencies] +cosmwasm-std = { git = "https://github.com/CosmWasm/cosmwasm.git", rev = "v2.2.0", features = [ + "staking", + "stargate", + "iterator", +] } +cosmwasm-vm = { git = "https://github.com/CosmWasm/cosmwasm.git", rev = "v2.2.0", features = [ + "staking", + "stargate", + "iterator", +] } +errno = "0.3.8" +rmp-serde = "1.3.0" +serde = { version = "1.0.103", features = ["derive"] } +serde_json = "1.0.91" +thiserror = "1.0.38" +hex = "0.4.3" +time = { version = "0.3.36", features = ["formatting"] } + +[dev-dependencies] +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +tempfile = "3.4.0" + +[build-dependencies] +cbindgen = "0.27.0" + +[profile.release] +opt-level = 3 +debug = false +rpath = true +lto = false +debug-assertions = false +codegen-units = 16 +panic = 'unwind' +incremental = true +overflow-checks = true diff --git a/libwasmvm/artifacts/.gitkeep b/libwasmvm/artifacts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/libwasmvm/bindings.h b/libwasmvm/bindings.h new file mode 100644 index 000000000..1f356a7fc --- /dev/null +++ b/libwasmvm/bindings.h @@ -0,0 +1,645 @@ +/* Licensed under Apache-2.0. Copyright see https://github.com/CosmWasm/wasmvm/blob/main/NOTICE. */ + +/* Generated with cbindgen:0.27.0 */ + +/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ + +#include +#include +#include +#include + +enum ErrnoValue { + ErrnoValue_Success = 0, + ErrnoValue_Other = 1, + ErrnoValue_OutOfGas = 2, +}; +typedef int32_t ErrnoValue; + +/** + * This enum gives names to the status codes returned from Go callbacks to Rust. + * The Go code will return one of these variants when returning. + * + * 0 means no error, all the other cases are some sort of error. + * + */ +enum GoError { + GoError_None = 0, + /** + * Go panicked for an unexpected reason. + */ + GoError_Panic = 1, + /** + * Go received a bad argument from Rust + */ + GoError_BadArgument = 2, + /** + * Ran out of gas while using the SDK (e.g. storage). This can come from the Cosmos SDK gas meter + * (https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/store/types/gas.go#L29-L32). + */ + GoError_OutOfGas = 3, + /** + * Error while trying to serialize data in Go code (typically json.Marshal) + */ + GoError_CannotSerialize = 4, + /** + * An error happened during normal operation of a Go callback, which should be fed back to the contract + */ + GoError_User = 5, + /** + * An error type that should never be created by us. It only serves as a fallback for the i32 to GoError conversion. + */ + GoError_Other = -1, +}; +typedef int32_t GoError; + +typedef struct cache_t { + +} cache_t; + +/** + * A view into an externally owned byte slice (Go `[]byte`). + * Use this for the current call only. A view cannot be copied for safety reasons. + * If you need a copy, use [`ByteSliceView::to_owned`]. + * + * Go's nil value is fully supported, such that we can differentiate between nil and an empty slice. + */ +typedef struct ByteSliceView { + /** + * True if and only if the byte slice is nil in Go. If this is true, the other fields must be ignored. + */ + bool is_nil; + const uint8_t *ptr; + uintptr_t len; +} ByteSliceView; + +/** + * An optional Vector type that requires explicit creation and destruction + * and can be sent via FFI. + * It can be created from `Option>` and be converted into `Option>`. + * + * This type is always created in Rust and always dropped in Rust. + * If Go code want to create it, it must instruct Rust to do so via the + * [`new_unmanaged_vector`] FFI export. If Go code wants to consume its data, + * it must create a copy and instruct Rust to destroy it via the + * [`destroy_unmanaged_vector`] FFI export. + * + * An UnmanagedVector is immutable. + * + * ## Ownership + * + * Ownership is the right and the obligation to destroy an `UnmanagedVector` + * exactly once. Both Rust and Go can create an `UnmanagedVector`, which gives + * then ownership. Sometimes it is necessary to transfer ownership. + * + * ### Transfer ownership from Rust to Go + * + * When an `UnmanagedVector` was created in Rust using [`UnmanagedVector::new`], [`UnmanagedVector::default`] + * or [`new_unmanaged_vector`], it can be passed to Go as a return value (see e.g. [load_wasm][crate::load_wasm]). + * Rust then has no chance to destroy the vector anymore, so ownership is transferred to Go. + * In Go, the data has to be copied to a garbage collected `[]byte`. Then the vector must be destroyed + * using [`destroy_unmanaged_vector`]. + * + * ### Transfer ownership from Go to Rust + * + * When Rust code calls into Go (using the vtable methods), return data or error messages must be created + * in Go. This is done by calling [`new_unmanaged_vector`] from Go, which copies data into a newly created + * `UnmanagedVector`. Since Go created it, it owns it. The ownership is then passed to Rust via the + * mutable return value pointers. On the Rust side, the vector is destroyed using [`UnmanagedVector::consume`]. + * + * ## Examples + * + * Transferring ownership from Rust to Go using return values of FFI calls: + * + * ``` + * # use wasmvm::{cache_t, ByteSliceView, UnmanagedVector}; + * #[no_mangle] + * pub extern "C" fn save_wasm_to_cache( + * cache: *mut cache_t, + * wasm: ByteSliceView, + * error_msg: Option<&mut UnmanagedVector>, + * ) -> UnmanagedVector { + * # let checksum: Vec = Default::default(); + * // some operation producing a `let checksum: Vec` + * + * UnmanagedVector::new(Some(checksum)) // this unmanaged vector is owned by the caller + * } + * ``` + * + * Transferring ownership from Go to Rust using return value pointers: + * + * ```rust + * # use cosmwasm_vm::{BackendResult, GasInfo}; + * # use wasmvm::{Db, GoError, U8SliceView, UnmanagedVector}; + * fn db_read(db: &Db, key: &[u8]) -> BackendResult>> { + * + * // Create a None vector in order to reserve memory for the result + * let mut output = UnmanagedVector::default(); + * + * // … + * # let mut error_msg = UnmanagedVector::default(); + * # let mut used_gas = 0_u64; + * # let read_db = db.vtable.read_db.unwrap(); + * + * let go_error: GoError = read_db( + * db.state, + * db.gas_meter, + * &mut used_gas as *mut u64, + * U8SliceView::new(Some(key)), + * // Go will create a new UnmanagedVector and override this address + * &mut output as *mut UnmanagedVector, + * &mut error_msg as *mut UnmanagedVector, + * ) + * .into(); + * + * // We now own the new UnmanagedVector written to the pointer and must destroy it + * let value = output.consume(); + * + * // Some gas processing and error handling + * # let gas_info = GasInfo::free(); + * + * (Ok(value), gas_info) + * } + * ``` + * + * + * If you want to mutate data, you need to consume the vector and create a new one: + * + * ```rust + * # use wasmvm::{UnmanagedVector}; + * # let input = UnmanagedVector::new(Some(vec![0xAA])); + * let mut mutable: Vec = input.consume().unwrap_or_default(); + * assert_eq!(mutable, vec![0xAA]); + * + * // `input` is now gone and we cam do everything we want to `mutable`, + * // including operations that reallocate the underlying data. + * + * mutable.push(0xBB); + * mutable.push(0xCC); + * + * assert_eq!(mutable, vec![0xAA, 0xBB, 0xCC]); + * + * let output = UnmanagedVector::new(Some(mutable)); + * + * // `output` is ready to be passed around + * ``` + */ +typedef struct UnmanagedVector { + /** + * True if and only if this is None. If this is true, the other fields must be ignored. + */ + bool is_none; + uint8_t *ptr; + uintptr_t len; + uintptr_t cap; +} UnmanagedVector; + +/** + * A version of `Option` that can be used safely in FFI. + */ +typedef struct OptionalU64 { + bool is_some; + uint64_t value; +} OptionalU64; + +/** + * The result type of the FFI function analyze_code. + * + * Please note that the unmanaged vector in `required_capabilities` + * has to be destroyed exactly once. When calling `analyze_code` + * from Go this is done via `C.destroy_unmanaged_vector`. + */ +typedef struct AnalysisReport { + /** + * `true` if and only if all required ibc exports exist as exported functions. + * This does not guarantee they are functional or even have the correct signatures. + */ + bool has_ibc_entry_points; + /** + * A UTF-8 encoded comma separated list of all entrypoints that + * are exported by the contract. + */ + struct UnmanagedVector entrypoints; + /** + * An UTF-8 encoded comma separated list of required capabilities. + * This is never None/nil. + */ + struct UnmanagedVector required_capabilities; + /** + * The migrate version of the contract. + * This is None if the contract does not have a migrate version and the `migrate` entrypoint + * needs to be called for every migration (if present). + * If it is `Some(version)`, it only needs to be called if the `version` increased. + */ + struct OptionalU64 contract_migrate_version; +} AnalysisReport; + +typedef struct Metrics { + uint32_t hits_pinned_memory_cache; + uint32_t hits_memory_cache; + uint32_t hits_fs_cache; + uint32_t misses; + uint64_t elements_pinned_memory_cache; + uint64_t elements_memory_cache; + uint64_t size_pinned_memory_cache; + uint64_t size_memory_cache; +} Metrics; + +/** + * An opaque type. `*gas_meter_t` represents a pointer to Go memory holding the gas meter. + */ +typedef struct gas_meter_t { + uint8_t _private[0]; +} gas_meter_t; + +typedef struct db_t { + uint8_t _private[0]; +} db_t; + +/** + * A view into a `Option<&[u8]>`, created and maintained by Rust. + * + * This can be copied into a []byte in Go. + */ +typedef struct U8SliceView { + /** + * True if and only if this is None. If this is true, the other fields must be ignored. + */ + bool is_none; + const uint8_t *ptr; + uintptr_t len; +} U8SliceView; + +/** + * A reference to some tables on the Go side which allow accessing + * the actual iterator instance. + */ +typedef struct IteratorReference { + /** + * An ID assigned to this contract call + */ + uint64_t call_id; + /** + * An ID assigned to this iterator + */ + uint64_t iterator_id; +} IteratorReference; + +typedef struct IteratorVtable { + int32_t (*next)(struct IteratorReference iterator, + struct gas_meter_t *gas_meter, + uint64_t *gas_used, + struct UnmanagedVector *key_out, + struct UnmanagedVector *value_out, + struct UnmanagedVector *err_msg_out); + int32_t (*next_key)(struct IteratorReference iterator, + struct gas_meter_t *gas_meter, + uint64_t *gas_used, + struct UnmanagedVector *key_out, + struct UnmanagedVector *err_msg_out); + int32_t (*next_value)(struct IteratorReference iterator, + struct gas_meter_t *gas_meter, + uint64_t *gas_used, + struct UnmanagedVector *value_out, + struct UnmanagedVector *err_msg_out); +} IteratorVtable; + +typedef struct GoIter { + struct gas_meter_t *gas_meter; + /** + * A reference which identifies the iterator and allows finding and accessing the + * actual iterator instance in Go. Once fully initialized, this is immutable. + */ + struct IteratorReference reference; + struct IteratorVtable vtable; +} GoIter; + +typedef struct DbVtable { + int32_t (*read_db)(struct db_t *db, + struct gas_meter_t *gas_meter, + uint64_t *gas_used, + struct U8SliceView key, + struct UnmanagedVector *value_out, + struct UnmanagedVector *err_msg_out); + int32_t (*write_db)(struct db_t *db, + struct gas_meter_t *gas_meter, + uint64_t *gas_used, + struct U8SliceView key, + struct U8SliceView value, + struct UnmanagedVector *err_msg_out); + int32_t (*remove_db)(struct db_t *db, + struct gas_meter_t *gas_meter, + uint64_t *gas_used, + struct U8SliceView key, + struct UnmanagedVector *err_msg_out); + int32_t (*scan_db)(struct db_t *db, + struct gas_meter_t *gas_meter, + uint64_t *gas_used, + struct U8SliceView start, + struct U8SliceView end, + int32_t order, + struct GoIter *iterator_out, + struct UnmanagedVector *err_msg_out); +} DbVtable; + +typedef struct Db { + struct gas_meter_t *gas_meter; + struct db_t *state; + struct DbVtable vtable; +} Db; + +typedef struct api_t { + uint8_t _private[0]; +} api_t; + +typedef struct GoApiVtable { + int32_t (*humanize_address)(const struct api_t *api, + struct U8SliceView input, + struct UnmanagedVector *humanized_address_out, + struct UnmanagedVector *err_msg_out, + uint64_t *gas_used); + int32_t (*canonicalize_address)(const struct api_t *api, + struct U8SliceView input, + struct UnmanagedVector *canonicalized_address_out, + struct UnmanagedVector *err_msg_out, + uint64_t *gas_used); + int32_t (*validate_address)(const struct api_t *api, + struct U8SliceView input, + struct UnmanagedVector *err_msg_out, + uint64_t *gas_used); +} GoApiVtable; + +typedef struct GoApi { + const struct api_t *state; + struct GoApiVtable vtable; +} GoApi; + +typedef struct querier_t { + uint8_t _private[0]; +} querier_t; + +typedef struct QuerierVtable { + int32_t (*query_external)(const struct querier_t *querier, + uint64_t gas_limit, + uint64_t *gas_used, + struct U8SliceView request, + struct UnmanagedVector *result_out, + struct UnmanagedVector *err_msg_out); +} QuerierVtable; + +typedef struct GoQuerier { + const struct querier_t *state; + struct QuerierVtable vtable; +} GoQuerier; + +typedef struct GasReport { + /** + * The original limit the instance was created with + */ + uint64_t limit; + /** + * The remaining gas that can be spend + */ + uint64_t remaining; + /** + * The amount of gas that was spend and metered externally in operations triggered by this instance + */ + uint64_t used_externally; + /** + * The amount of gas that was spend and metered internally (i.e. by executing Wasm and calling + * API methods which are not metered externally) + */ + uint64_t used_internally; +} GasReport; + +struct cache_t *init_cache(struct ByteSliceView config, struct UnmanagedVector *error_msg); + +struct UnmanagedVector store_code(struct cache_t *cache, + struct ByteSliceView wasm, + bool checked, + bool persist, + struct UnmanagedVector *error_msg); + +void remove_wasm(struct cache_t *cache, + struct ByteSliceView checksum, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector load_wasm(struct cache_t *cache, + struct ByteSliceView checksum, + struct UnmanagedVector *error_msg); + +void pin(struct cache_t *cache, struct ByteSliceView checksum, struct UnmanagedVector *error_msg); + +void unpin(struct cache_t *cache, struct ByteSliceView checksum, struct UnmanagedVector *error_msg); + +struct AnalysisReport analyze_code(struct cache_t *cache, + struct ByteSliceView checksum, + struct UnmanagedVector *error_msg); + +struct Metrics get_metrics(struct cache_t *cache, struct UnmanagedVector *error_msg); + +struct UnmanagedVector get_pinned_metrics(struct cache_t *cache, struct UnmanagedVector *error_msg); + +/** + * frees a cache reference + * + * # Safety + * + * This must be called exactly once for any `*cache_t` returned by `init_cache` + * and cannot be called on any other pointer. + */ +void release_cache(struct cache_t *cache); + +struct UnmanagedVector instantiate(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView info, + struct ByteSliceView msg, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector execute(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView info, + struct ByteSliceView msg, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector migrate(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector migrate_with_info(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct ByteSliceView migrate_info, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector sudo(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector reply(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector query(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector ibc_channel_open(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector ibc_channel_connect(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector ibc_channel_close(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector ibc_packet_receive(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector ibc_packet_ack(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector ibc_packet_timeout(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector ibc_source_callback(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector ibc_destination_callback(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector new_unmanaged_vector(bool nil, const uint8_t *ptr, uintptr_t length); + +void destroy_unmanaged_vector(struct UnmanagedVector v); + +/** + * Returns a version number of this library as a C string. + * + * The string is owned by libwasmvm and must not be mutated or destroyed by the caller. + */ +const char *version_str(void); diff --git a/libwasmvm/build.rs b/libwasmvm/build.rs new file mode 100644 index 000000000..696e0e012 --- /dev/null +++ b/libwasmvm/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + cbindgen::generate(crate_dir) + .expect("Unable to generate bindings") + .write_to_file("bindings.h"); +} diff --git a/libwasmvm/cbindgen.toml b/libwasmvm/cbindgen.toml new file mode 100644 index 000000000..c10bd1245 --- /dev/null +++ b/libwasmvm/cbindgen.toml @@ -0,0 +1,146 @@ +# This is a template cbindgen.toml file with all of the default values. +# Some values are commented out because their absence is the real default. +# +# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml +# for detailed documentation of every option here. + + + +language = "C" + + +############## Options for Wrapping the Contents of the Header ################# + +header = "/* Licensed under Apache-2.0. Copyright see https://github.com/CosmWasm/wasmvm/blob/main/NOTICE. */" +# trailer = "/* Text to put at the end of the generated file */" +# include_guard = "my_bindings_h" +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +include_version = true +# namespace = "my_namespace" +namespaces = [] +sys_includes = [] +includes = [] +no_includes = false + + + + +############################ Code Style Options ################################ + +braces = "SameLine" +line_length = 100 +tab_width = 2 +documentation_style = "auto" + + + + + +############################# Codegen Options ################################## + +style = "both" + + + +[defines] +# "target_os = freebsd" = "DEFINE_FREEBSD" +# "feature = serde" = "DEFINE_SERDE" + + + +[export] +include = [ + "GoError", + "ErrnoValue", +] +exclude = [] +# prefix = "CAPI_" +item_types = [] +renaming_overrides_prefixing = false + + + +[export.rename] + + + +[export.body] + + + + +[fn] +rename_args = "None" +# must_use = "MUST_USE_FUNC" +# prefix = "START_FUNC" +# postfix = "END_FUNC" +args = "auto" + + + + +[struct] +rename_fields = "None" +# must_use = "MUST_USE_STRUCT" +derive_constructor = false +derive_eq = false +derive_neq = false +derive_lt = false +derive_lte = false +derive_gt = false +derive_gte = false + + + + +[enum] +rename_variants = "None" +# must_use = "MUST_USE_ENUM" +add_sentinel = false +prefix_with_name = false +derive_helper_methods = false +derive_const_casts = false +derive_mut_casts = false +# cast_assert_name = "ASSERT" +derive_tagged_enum_destructor = false +derive_tagged_enum_copy_constructor = false +private_default_tagged_enum_constructor = false + + + + +[const] +allow_static_const = true + + + + +[macro_expansion] +bitflags = false + + + + + + +############## Options for How Your Rust library Should Be Parsed ############## + +[parse] +parse_deps = false +# include = [] +exclude = [] +clean = false +extra_bindings = [] + + + +[parse.expand] +crates = [] +all_features = false +default_features = true +features = [] + + + + + diff --git a/libwasmvm/clippy.toml b/libwasmvm/clippy.toml new file mode 100644 index 000000000..85355417c --- /dev/null +++ b/libwasmvm/clippy.toml @@ -0,0 +1 @@ +too-many-arguments-threshold = 13 diff --git a/libwasmvm/src/api.rs b/libwasmvm/src/api.rs new file mode 100644 index 000000000..a50a43f88 --- /dev/null +++ b/libwasmvm/src/api.rs @@ -0,0 +1,159 @@ +use cosmwasm_vm::{BackendApi, BackendError, BackendResult, GasInfo}; + +use crate::error::GoError; +use crate::memory::{U8SliceView, UnmanagedVector}; +use crate::Vtable; + +// this represents something passed in from the caller side of FFI +// in this case a struct with go function pointers +#[repr(C)] +pub struct api_t { + _private: [u8; 0], +} + +// These functions should return GoError but because we don't trust them here, we treat the return value as i32 +// and then check it when converting to GoError manually +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct GoApiVtable { + pub humanize_address: Option< + extern "C" fn( + api: *const api_t, + input: U8SliceView, + humanized_address_out: *mut UnmanagedVector, + err_msg_out: *mut UnmanagedVector, + gas_used: *mut u64, + ) -> i32, + >, + pub canonicalize_address: Option< + extern "C" fn( + api: *const api_t, + input: U8SliceView, + canonicalized_address_out: *mut UnmanagedVector, + err_msg_out: *mut UnmanagedVector, + gas_used: *mut u64, + ) -> i32, + >, + pub validate_address: Option< + extern "C" fn( + api: *const api_t, + input: U8SliceView, + err_msg_out: *mut UnmanagedVector, + gas_used: *mut u64, + ) -> i32, + >, +} + +impl Vtable for GoApiVtable {} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct GoApi { + pub state: *const api_t, + pub vtable: GoApiVtable, +} + +// We must declare that these are safe to Send, to use in wasm. +// The known go caller passes in immutable function pointers, but this is indeed +// unsafe for possible other callers. +// +// see: https://stackoverflow.com/questions/50258359/can-a-struct-containing-a-raw-pointer-implement-send-and-be-ffi-safe +unsafe impl Send for GoApi {} + +impl BackendApi for GoApi { + fn addr_canonicalize(&self, human: &str) -> BackendResult> { + let mut output = UnmanagedVector::default(); + let mut error_msg = UnmanagedVector::default(); + let mut used_gas = 0_u64; + let canonicalize_address = self + .vtable + .canonicalize_address + .expect("vtable function 'canonicalize_address' not set"); + let go_error: GoError = canonicalize_address( + self.state, + U8SliceView::new(Some(human.as_bytes())), + &mut output as *mut UnmanagedVector, + &mut error_msg as *mut UnmanagedVector, + &mut used_gas as *mut u64, + ) + .into(); + // We destruct the UnmanagedVector here, no matter if we need the data. + let output = output.consume(); + + let gas_info = GasInfo::with_cost(used_gas); + + // return complete error message (reading from buffer for GoError::Other) + let default = || format!("Failed to canonicalize the address: {human}"); + unsafe { + if let Err(err) = go_error.into_result(error_msg, default) { + return (Err(err), gas_info); + } + } + + let result = output.ok_or_else(|| BackendError::unknown("Unset output")); + (result, gas_info) + } + + fn addr_humanize(&self, canonical: &[u8]) -> BackendResult { + let mut output = UnmanagedVector::default(); + let mut error_msg = UnmanagedVector::default(); + let mut used_gas = 0_u64; + let humanize_address = self + .vtable + .humanize_address + .expect("vtable function 'humanize_address' not set"); + let go_error: GoError = humanize_address( + self.state, + U8SliceView::new(Some(canonical)), + &mut output as *mut UnmanagedVector, + &mut error_msg as *mut UnmanagedVector, + &mut used_gas as *mut u64, + ) + .into(); + // We destruct the UnmanagedVector here, no matter if we need the data. + let output = output.consume(); + + let gas_info = GasInfo::with_cost(used_gas); + + // return complete error message (reading from buffer for GoError::Other) + let default = || { + format!( + "Failed to humanize the address: {}", + hex::encode_upper(canonical) + ) + }; + unsafe { + if let Err(err) = go_error.into_result(error_msg, default) { + return (Err(err), gas_info); + } + } + + let result = output + .ok_or_else(|| BackendError::unknown("Unset output")) + .and_then(|human_data| String::from_utf8(human_data).map_err(BackendError::from)); + (result, gas_info) + } + + fn addr_validate(&self, input: &str) -> BackendResult<()> { + let mut error_msg = UnmanagedVector::default(); + let mut used_gas = 0_u64; + let validate_address = self + .vtable + .validate_address + .expect("vtable function 'validate_address' not set"); + let go_error: GoError = validate_address( + self.state, + U8SliceView::new(Some(input.as_bytes())), + &mut error_msg as *mut UnmanagedVector, + &mut used_gas as *mut u64, + ) + .into(); + + let gas_info = GasInfo::with_cost(used_gas); + + // return complete error message (reading from buffer for GoError::Other) + let default = || format!("Failed to validate the address: {input}"); + let result = unsafe { go_error.into_result(error_msg, default) }; + (result, gas_info) + } +} diff --git a/libwasmvm/src/args.rs b/libwasmvm/src/args.rs new file mode 100644 index 000000000..480867259 --- /dev/null +++ b/libwasmvm/src/args.rs @@ -0,0 +1,9 @@ +// store some common string for argument names +pub const CONFIG_ARG: &str = "config"; +pub const CACHE_ARG: &str = "cache"; +pub const WASM_ARG: &str = "wasm"; +pub const CHECKSUM_ARG: &str = "checksum"; +pub const GAS_REPORT_ARG: &str = "gas_report"; +pub const ARG1: &str = "arg1"; +pub const ARG2: &str = "arg2"; +pub const ARG3: &str = "arg3"; diff --git a/libwasmvm/src/cache.rs b/libwasmvm/src/cache.rs new file mode 100644 index 000000000..064abcd5b --- /dev/null +++ b/libwasmvm/src/cache.rs @@ -0,0 +1,1078 @@ +use std::collections::BTreeSet; +use std::convert::TryInto; +use std::panic::{catch_unwind, AssertUnwindSafe}; + +use cosmwasm_std::Checksum; +use cosmwasm_vm::Cache; +use serde::Serialize; + +use crate::api::GoApi; +use crate::args::{CACHE_ARG, CHECKSUM_ARG, CONFIG_ARG, WASM_ARG}; +use crate::error::{handle_c_error_binary, handle_c_error_default, handle_c_error_ptr, Error}; +use crate::handle_vm_panic::handle_vm_panic; +use crate::memory::{ByteSliceView, UnmanagedVector}; +use crate::querier::GoQuerier; +use crate::storage::GoStorage; + +#[repr(C)] +pub struct cache_t {} + +pub fn to_cache(ptr: *mut cache_t) -> Option<&'static mut Cache> { + if ptr.is_null() { + None + } else { + let c = unsafe { &mut *(ptr as *mut Cache) }; + Some(c) + } +} + +#[no_mangle] +pub extern "C" fn init_cache( + config: ByteSliceView, + error_msg: Option<&mut UnmanagedVector>, +) -> *mut cache_t { + let r = catch_unwind(|| do_init_cache(config)).unwrap_or_else(|err| { + handle_vm_panic("do_init_cache", err); + Err(Error::panic()) + }); + handle_c_error_ptr(r, error_msg) as *mut cache_t +} + +fn do_init_cache(config: ByteSliceView) -> Result<*mut Cache, Error> { + let config = + serde_json::from_slice(config.read().ok_or_else(|| Error::unset_arg(CONFIG_ARG))?)?; + // parse the supported capabilities + let cache = unsafe { Cache::new_with_config(config) }?; + let out = Box::new(cache); + Ok(Box::into_raw(out)) +} + +#[no_mangle] +pub extern "C" fn store_code( + cache: *mut cache_t, + wasm: ByteSliceView, + checked: bool, + persist: bool, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + let r = match to_cache(cache) { + Some(c) => catch_unwind(AssertUnwindSafe(move || { + do_store_code(c, wasm, checked, persist) + })) + .unwrap_or_else(|err| { + handle_vm_panic("do_store_code", err); + Err(Error::panic()) + }), + None => Err(Error::unset_arg(CACHE_ARG)), + }; + let checksum = handle_c_error_binary(r, error_msg); + UnmanagedVector::new(Some(checksum)) +} + +fn do_store_code( + cache: &mut Cache, + wasm: ByteSliceView, + checked: bool, + persist: bool, +) -> Result { + let wasm = wasm.read().ok_or_else(|| Error::unset_arg(WASM_ARG))?; + Ok(cache.store_code(wasm, checked, persist)?) +} + +#[no_mangle] +pub extern "C" fn remove_wasm( + cache: *mut cache_t, + checksum: ByteSliceView, + error_msg: Option<&mut UnmanagedVector>, +) { + let r = match to_cache(cache) { + Some(c) => catch_unwind(AssertUnwindSafe(move || do_remove_wasm(c, checksum))) + .unwrap_or_else(|err| { + handle_vm_panic("do_remove_wasm", err); + Err(Error::panic()) + }), + None => Err(Error::unset_arg(CACHE_ARG)), + }; + handle_c_error_default(r, error_msg) +} + +fn do_remove_wasm( + cache: &mut Cache, + checksum: ByteSliceView, +) -> Result<(), Error> { + let checksum: Checksum = checksum + .read() + .ok_or_else(|| Error::unset_arg(CHECKSUM_ARG))? + .try_into()?; + cache.remove_wasm(&checksum)?; + Ok(()) +} + +#[no_mangle] +pub extern "C" fn load_wasm( + cache: *mut cache_t, + checksum: ByteSliceView, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + let r = match to_cache(cache) { + Some(c) => catch_unwind(AssertUnwindSafe(move || do_load_wasm(c, checksum))) + .unwrap_or_else(|err| { + handle_vm_panic("do_load_wasm", err); + Err(Error::panic()) + }), + None => Err(Error::unset_arg(CACHE_ARG)), + }; + let data = handle_c_error_binary(r, error_msg); + UnmanagedVector::new(Some(data)) +} + +fn do_load_wasm( + cache: &mut Cache, + checksum: ByteSliceView, +) -> Result, Error> { + let checksum: Checksum = checksum + .read() + .ok_or_else(|| Error::unset_arg(CHECKSUM_ARG))? + .try_into()?; + let wasm = cache.load_wasm(&checksum)?; + Ok(wasm) +} + +#[no_mangle] +pub extern "C" fn pin( + cache: *mut cache_t, + checksum: ByteSliceView, + error_msg: Option<&mut UnmanagedVector>, +) { + let r = match to_cache(cache) { + Some(c) => { + catch_unwind(AssertUnwindSafe(move || do_pin(c, checksum))).unwrap_or_else(|err| { + handle_vm_panic("do_pin", err); + Err(Error::panic()) + }) + } + None => Err(Error::unset_arg(CACHE_ARG)), + }; + handle_c_error_default(r, error_msg) +} + +fn do_pin( + cache: &mut Cache, + checksum: ByteSliceView, +) -> Result<(), Error> { + let checksum: Checksum = checksum + .read() + .ok_or_else(|| Error::unset_arg(CHECKSUM_ARG))? + .try_into()?; + cache.pin(&checksum)?; + Ok(()) +} + +#[no_mangle] +pub extern "C" fn unpin( + cache: *mut cache_t, + checksum: ByteSliceView, + error_msg: Option<&mut UnmanagedVector>, +) { + let r = match to_cache(cache) { + Some(c) => { + catch_unwind(AssertUnwindSafe(move || do_unpin(c, checksum))).unwrap_or_else(|err| { + handle_vm_panic("do_unpin", err); + Err(Error::panic()) + }) + } + None => Err(Error::unset_arg(CACHE_ARG)), + }; + handle_c_error_default(r, error_msg) +} + +fn do_unpin( + cache: &mut Cache, + checksum: ByteSliceView, +) -> Result<(), Error> { + let checksum: Checksum = checksum + .read() + .ok_or_else(|| Error::unset_arg(CHECKSUM_ARG))? + .try_into()?; + cache.unpin(&checksum)?; + Ok(()) +} + +/// The result type of the FFI function analyze_code. +/// +/// Please note that the unmanaged vector in `required_capabilities` +/// has to be destroyed exactly once. When calling `analyze_code` +/// from Go this is done via `C.destroy_unmanaged_vector`. +#[repr(C)] +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +pub struct AnalysisReport { + /// `true` if and only if all required ibc exports exist as exported functions. + /// This does not guarantee they are functional or even have the correct signatures. + pub has_ibc_entry_points: bool, + /// A UTF-8 encoded comma separated list of all entrypoints that + /// are exported by the contract. + pub entrypoints: UnmanagedVector, + /// An UTF-8 encoded comma separated list of required capabilities. + /// This is never None/nil. + pub required_capabilities: UnmanagedVector, + /// The migrate version of the contract. + /// This is None if the contract does not have a migrate version and the `migrate` entrypoint + /// needs to be called for every migration (if present). + /// If it is `Some(version)`, it only needs to be called if the `version` increased. + pub contract_migrate_version: OptionalU64, +} + +impl From for AnalysisReport { + fn from(report: cosmwasm_vm::AnalysisReport) -> Self { + let cosmwasm_vm::AnalysisReport { + has_ibc_entry_points, + required_capabilities, + entrypoints, + contract_migrate_version, + .. + } = report; + + let required_capabilities_utf8 = set_to_csv(required_capabilities).into_bytes(); + let entrypoints = set_to_csv(entrypoints).into_bytes(); + AnalysisReport { + has_ibc_entry_points, + required_capabilities: UnmanagedVector::new(Some(required_capabilities_utf8)), + entrypoints: UnmanagedVector::new(Some(entrypoints)), + contract_migrate_version: contract_migrate_version.into(), + } + } +} + +/// A version of `Option` that can be used safely in FFI. +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +#[repr(C)] +pub struct OptionalU64 { + is_some: bool, + value: u64, +} + +impl From> for OptionalU64 { + fn from(opt: Option) -> Self { + match opt { + None => OptionalU64 { + is_some: false, + value: 0, // value is ignored + }, + Some(value) => OptionalU64 { + is_some: true, + value, + }, + } + } +} + +/// Takes a set of string-like elements and returns a comma-separated list. +/// Since no escaping or quoting is applied to the elements, the caller needs to ensure +/// only simple alphanumeric values are used. +/// +/// The order of the output elements is determined by the iteration order of the provided set. +fn set_to_csv(set: BTreeSet>) -> String { + let list: Vec<&str> = set.iter().map(|e| e.as_ref()).collect(); + list.join(",") +} + +#[no_mangle] +pub extern "C" fn analyze_code( + cache: *mut cache_t, + checksum: ByteSliceView, + error_msg: Option<&mut UnmanagedVector>, +) -> AnalysisReport { + let r = match to_cache(cache) { + Some(c) => catch_unwind(AssertUnwindSafe(move || do_analyze_code(c, checksum))) + .unwrap_or_else(|err| { + handle_vm_panic("do_analyze_code", err); + Err(Error::panic()) + }), + None => Err(Error::unset_arg(CACHE_ARG)), + }; + handle_c_error_default(r, error_msg) +} + +fn do_analyze_code( + cache: &mut Cache, + checksum: ByteSliceView, +) -> Result { + let checksum: Checksum = checksum + .read() + .ok_or_else(|| Error::unset_arg(CHECKSUM_ARG))? + .try_into()?; + let report = cache.analyze(&checksum)?; + Ok(report.into()) +} + +#[repr(C)] +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +pub struct Metrics { + pub hits_pinned_memory_cache: u32, + pub hits_memory_cache: u32, + pub hits_fs_cache: u32, + pub misses: u32, + pub elements_pinned_memory_cache: u64, + pub elements_memory_cache: u64, + pub size_pinned_memory_cache: u64, + pub size_memory_cache: u64, +} + +impl From for Metrics { + fn from(report: cosmwasm_vm::Metrics) -> Self { + let cosmwasm_vm::Metrics { + stats: + cosmwasm_vm::Stats { + hits_pinned_memory_cache, + hits_memory_cache, + hits_fs_cache, + misses, + }, + elements_pinned_memory_cache, + elements_memory_cache, + size_pinned_memory_cache, + size_memory_cache, + } = report; + + Metrics { + hits_pinned_memory_cache, + hits_memory_cache, + hits_fs_cache, + misses, + elements_pinned_memory_cache: elements_pinned_memory_cache + .try_into() + .expect("usize is larger than 64 bit? Really?"), + elements_memory_cache: elements_memory_cache + .try_into() + .expect("usize is larger than 64 bit? Really?"), + size_pinned_memory_cache: size_pinned_memory_cache + .try_into() + .expect("usize is larger than 64 bit? Really?"), + size_memory_cache: size_memory_cache + .try_into() + .expect("usize is larger than 64 bit? Really?"), + } + } +} + +#[no_mangle] +pub extern "C" fn get_metrics( + cache: *mut cache_t, + error_msg: Option<&mut UnmanagedVector>, +) -> Metrics { + let r = match to_cache(cache) { + Some(c) => { + catch_unwind(AssertUnwindSafe(move || do_get_metrics(c))).unwrap_or_else(|err| { + handle_vm_panic("do_get_metrics", err); + Err(Error::panic()) + }) + } + None => Err(Error::unset_arg(CACHE_ARG)), + }; + handle_c_error_default(r, error_msg) +} + +#[allow(clippy::unnecessary_wraps)] // Keep unused Result for consistent boilerplate for all fn do_* +fn do_get_metrics(cache: &mut Cache) -> Result { + Ok(cache.metrics().into()) +} + +#[derive(Serialize)] +struct PerModuleMetrics { + hits: u32, + size: usize, +} + +impl From for PerModuleMetrics { + fn from(value: cosmwasm_vm::PerModuleMetrics) -> Self { + Self { + hits: value.hits, + size: value.size, + } + } +} + +#[derive(Serialize)] +struct PinnedMetrics { + // TODO: Remove the array usage as soon as `Checksum` has a stable wire format in msgpack + per_module: Vec<([u8; 32], PerModuleMetrics)>, +} + +impl From for PinnedMetrics { + fn from(value: cosmwasm_vm::PinnedMetrics) -> Self { + Self { + per_module: value + .per_module + .into_iter() + .map(|(checksum, metrics)| (*checksum.as_ref(), metrics.into())) + .collect(), + } + } +} + +#[no_mangle] +pub extern "C" fn get_pinned_metrics( + cache: *mut cache_t, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + let r = match to_cache(cache) { + Some(c) => { + catch_unwind(AssertUnwindSafe(move || do_get_pinned_metrics(c))).unwrap_or_else(|err| { + handle_vm_panic("do_get_pinned_metrics", err); + Err(Error::panic()) + }) + } + None => Err(Error::unset_arg(CACHE_ARG)), + }; + handle_c_error_default(r, error_msg) +} + +fn do_get_pinned_metrics( + cache: &mut Cache, +) -> Result { + let pinned_metrics = PinnedMetrics::from(cache.pinned_metrics()); + let edgerunner = rmp_serde::to_vec(&pinned_metrics)?; + Ok(UnmanagedVector::new(Some(edgerunner))) +} + +/// frees a cache reference +/// +/// # Safety +/// +/// This must be called exactly once for any `*cache_t` returned by `init_cache` +/// and cannot be called on any other pointer. +#[no_mangle] +pub extern "C" fn release_cache(cache: *mut cache_t) { + if !cache.is_null() { + // this will free cache when it goes out of scope + let _ = unsafe { Box::from_raw(cache as *mut Cache) }; + } +} + +#[cfg(test)] +mod tests { + use crate::assert_approx_eq; + + use super::*; + use cosmwasm_vm::{CacheOptions, Config, Size}; + use std::{cmp::Ordering, collections::HashSet, iter::FromIterator, path::PathBuf}; + use tempfile::TempDir; + + static HACKATOM: &[u8] = include_bytes!("../../testdata/hackatom.wasm"); + static IBC_REFLECT: &[u8] = include_bytes!("../../testdata/ibc_reflect.wasm"); + + #[test] + fn init_cache_and_release_cache_work() { + let dir: String = TempDir::new().unwrap().path().to_str().unwrap().to_owned(); + let capabilities = "staking".to_string(); + + let mut error_msg = UnmanagedVector::default(); + let config = Config::new(CacheOptions::new( + dir, + [capabilities], + Size::mebi(512), + Size::mebi(32), + )); + let config = serde_json::to_vec(&config).unwrap(); + let cache_ptr = init_cache(ByteSliceView::new(config.as_slice()), Some(&mut error_msg)); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + release_cache(cache_ptr); + } + + #[test] + fn init_cache_writes_error() { + let dir: String = String::from("broken\0dir"); // null bytes are valid UTF8 but not allowed in FS paths + let capabilities = "staking".to_string(); + + let mut error_msg = UnmanagedVector::default(); + let config = Config::new(CacheOptions::new( + dir, + [capabilities], + Size::mebi(512), + Size::mebi(32), + )); + let config = serde_json::to_vec(&config).unwrap(); + let cache_ptr = init_cache(ByteSliceView::new(config.as_slice()), Some(&mut error_msg)); + assert!(cache_ptr.is_null()); + assert!(error_msg.is_some()); + let msg = String::from_utf8(error_msg.consume().unwrap()).unwrap(); + assert_eq!( + msg, + "Error calling the VM: Cache error: Error creating state directory" + ); + } + + #[test] + fn save_wasm_works() { + let dir: String = TempDir::new().unwrap().path().to_str().unwrap().to_owned(); + let capabilities = "staking".to_string(); + + let mut error_msg = UnmanagedVector::default(); + let config = Config::new(CacheOptions::new( + dir, + [capabilities], + Size::mebi(512), + Size::mebi(32), + )); + let config = serde_json::to_vec(&config).unwrap(); + let cache_ptr = init_cache(ByteSliceView::new(config.as_slice()), Some(&mut error_msg)); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + let mut error_msg = UnmanagedVector::default(); + store_code( + cache_ptr, + ByteSliceView::new(HACKATOM), + false, + true, + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + release_cache(cache_ptr); + } + + #[test] + fn remove_wasm_works() { + let dir: String = TempDir::new().unwrap().path().to_str().unwrap().to_owned(); + let capabilities = "staking".to_string(); + + let mut error_msg = UnmanagedVector::default(); + let config = Config::new(CacheOptions::new( + dir, + [capabilities], + Size::mebi(512), + Size::mebi(32), + )); + let config = serde_json::to_vec(&config).unwrap(); + let cache_ptr = init_cache(ByteSliceView::new(config.as_slice()), Some(&mut error_msg)); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + let mut error_msg = UnmanagedVector::default(); + let checksum = store_code( + cache_ptr, + ByteSliceView::new(HACKATOM), + false, + true, + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + let checksum = checksum.consume().unwrap_or_default(); + + // Removing once works + let mut error_msg = UnmanagedVector::default(); + remove_wasm( + cache_ptr, + ByteSliceView::new(&checksum), + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + // Removing again fails + let mut error_msg = UnmanagedVector::default(); + remove_wasm( + cache_ptr, + ByteSliceView::new(&checksum), + Some(&mut error_msg), + ); + let error_msg = error_msg + .consume() + .map(|e| String::from_utf8_lossy(&e).into_owned()); + assert_eq!( + error_msg.unwrap(), + "Error calling the VM: Cache error: Wasm file does not exist" + ); + + release_cache(cache_ptr); + } + + #[test] + fn load_wasm_works() { + let dir: String = TempDir::new().unwrap().path().to_str().unwrap().to_owned(); + let capabilities = "staking".to_string(); + + let mut error_msg = UnmanagedVector::default(); + let config = Config::new(CacheOptions::new( + dir, + [capabilities], + Size::mebi(512), + Size::mebi(32), + )); + let config = serde_json::to_vec(&config).unwrap(); + let cache_ptr = init_cache(ByteSliceView::new(config.as_slice()), Some(&mut error_msg)); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + let mut error_msg = UnmanagedVector::default(); + let checksum = store_code( + cache_ptr, + ByteSliceView::new(HACKATOM), + false, + true, + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + let checksum = checksum.consume().unwrap_or_default(); + + let mut error_msg = UnmanagedVector::default(); + let wasm = load_wasm( + cache_ptr, + ByteSliceView::new(&checksum), + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + let wasm = wasm.consume().unwrap_or_default(); + assert_eq!(wasm, HACKATOM); + + release_cache(cache_ptr); + } + + #[test] + fn pin_works() { + let dir: String = TempDir::new().unwrap().path().to_str().unwrap().to_owned(); + let capabilities = "staking".to_string(); + + let mut error_msg = UnmanagedVector::default(); + let config = Config::new(CacheOptions::new( + dir, + [capabilities], + Size::mebi(512), + Size::mebi(32), + )); + let config = serde_json::to_vec(&config).unwrap(); + let cache_ptr = init_cache(ByteSliceView::new(config.as_slice()), Some(&mut error_msg)); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + let mut error_msg = UnmanagedVector::default(); + let checksum = store_code( + cache_ptr, + ByteSliceView::new(HACKATOM), + false, + true, + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + let checksum = checksum.consume().unwrap_or_default(); + + let mut error_msg = UnmanagedVector::default(); + pin( + cache_ptr, + ByteSliceView::new(&checksum), + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + // pinning again has no effect + let mut error_msg = UnmanagedVector::default(); + pin( + cache_ptr, + ByteSliceView::new(&checksum), + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + release_cache(cache_ptr); + } + + #[test] + fn unpin_works() { + let dir: String = TempDir::new().unwrap().path().to_str().unwrap().to_owned(); + let capabilities = "staking".to_string(); + + let mut error_msg = UnmanagedVector::default(); + let config = Config::new(CacheOptions::new( + dir, + [capabilities], + Size::mebi(512), + Size::mebi(32), + )); + let config = serde_json::to_vec(&config).unwrap(); + let cache_ptr = init_cache(ByteSliceView::new(config.as_slice()), Some(&mut error_msg)); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + let mut error_msg = UnmanagedVector::default(); + let checksum = store_code( + cache_ptr, + ByteSliceView::new(HACKATOM), + false, + true, + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + let checksum = checksum.consume().unwrap_or_default(); + + let mut error_msg = UnmanagedVector::default(); + pin( + cache_ptr, + ByteSliceView::new(&checksum), + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + let mut error_msg = UnmanagedVector::default(); + unpin( + cache_ptr, + ByteSliceView::new(&checksum), + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + // Unpinning again has no effect + let mut error_msg = UnmanagedVector::default(); + unpin( + cache_ptr, + ByteSliceView::new(&checksum), + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + release_cache(cache_ptr); + } + + #[test] + fn analyze_code_works() { + let dir: String = TempDir::new().unwrap().path().to_str().unwrap().to_owned(); + let capabilities = ["staking", "stargate", "iterator"].map(|c| c.to_string()); + + let mut error_msg = UnmanagedVector::default(); + let config = Config::new(CacheOptions::new( + dir, + capabilities, + Size::mebi(512), + Size::mebi(32), + )); + let config = serde_json::to_vec(&config).unwrap(); + let cache_ptr = init_cache(ByteSliceView::new(config.as_slice()), Some(&mut error_msg)); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + let mut error_msg = UnmanagedVector::default(); + let checksum_hackatom = store_code( + cache_ptr, + ByteSliceView::new(HACKATOM), + false, + true, + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + let checksum_hackatom = checksum_hackatom.consume().unwrap_or_default(); + + let mut error_msg = UnmanagedVector::default(); + let checksum_ibc_reflect = store_code( + cache_ptr, + ByteSliceView::new(IBC_REFLECT), + false, + true, + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + let checksum_ibc_reflect = checksum_ibc_reflect.consume().unwrap_or_default(); + + let mut error_msg = UnmanagedVector::default(); + let hackatom_report = analyze_code( + cache_ptr, + ByteSliceView::new(&checksum_hackatom), + Some(&mut error_msg), + ); + let _ = error_msg.consume(); + assert!(!hackatom_report.has_ibc_entry_points); + assert_eq!( + hackatom_report.required_capabilities.consume().unwrap(), + b"" + ); + assert!(hackatom_report.contract_migrate_version.is_some); + assert_eq!(hackatom_report.contract_migrate_version.value, 42); + + let mut error_msg: UnmanagedVector = UnmanagedVector::default(); + let ibc_reflect_report = analyze_code( + cache_ptr, + ByteSliceView::new(&checksum_ibc_reflect), + Some(&mut error_msg), + ); + let _ = error_msg.consume(); + assert!(ibc_reflect_report.has_ibc_entry_points); + let required_capabilities = + String::from_utf8_lossy(&ibc_reflect_report.required_capabilities.consume().unwrap()) + .to_string(); + assert_eq!(required_capabilities, "iterator,stargate"); + + release_cache(cache_ptr); + } + + #[test] + fn set_to_csv_works() { + assert_eq!(set_to_csv(BTreeSet::::new()), ""); + assert_eq!( + set_to_csv(BTreeSet::from_iter(vec!["foo".to_string()])), + "foo", + ); + assert_eq!( + set_to_csv(BTreeSet::from_iter(vec![ + "foo".to_string(), + "bar".to_string(), + "baz".to_string(), + ])), + "bar,baz,foo", + ); + assert_eq!( + set_to_csv(BTreeSet::from_iter(vec![ + "a".to_string(), + "aa".to_string(), + "b".to_string(), + "c".to_string(), + "A".to_string(), + "AA".to_string(), + "B".to_string(), + "C".to_string(), + ])), + "A,AA,B,C,a,aa,b,c", + ); + + // str + assert_eq!( + set_to_csv(BTreeSet::from_iter(vec![ + "a", "aa", "b", "c", "A", "AA", "B", "C", + ])), + "A,AA,B,C,a,aa,b,c", + ); + + // custom type with numeric ordering + #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] + enum Number { + One = 1, + Two = 2, + Three = 3, + Eleven = 11, + Twelve = 12, + Zero = 0, + MinusOne = -1, + } + + impl AsRef for Number { + fn as_ref(&self) -> &str { + use Number::*; + match self { + One => "1", + Two => "2", + Three => "3", + Eleven => "11", + Twelve => "12", + Zero => "0", + MinusOne => "-1", + } + } + } + + assert_eq!( + set_to_csv(BTreeSet::from_iter([ + Number::One, + Number::Two, + Number::Three, + Number::Eleven, + Number::Twelve, + Number::Zero, + Number::MinusOne, + ])), + "-1,0,1,2,3,11,12", + ); + + // custom type with lexicographical ordering + #[derive(PartialEq, Eq)] + enum Color { + Red, + Green, + Blue, + Yellow, + } + + impl AsRef for Color { + fn as_ref(&self) -> &str { + use Color::*; + match self { + Red => "red", + Green => "green", + Blue => "blue", + Yellow => "yellow", + } + } + } + + impl Ord for Color { + fn cmp(&self, other: &Self) -> Ordering { + // sort by name + self.as_ref().cmp(other.as_ref()) + } + } + + impl PartialOrd for Color { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + assert_eq!( + set_to_csv(BTreeSet::from_iter([ + Color::Red, + Color::Green, + Color::Blue, + Color::Yellow, + ])), + "blue,green,red,yellow", + ); + } + + #[test] + fn get_metrics_works() { + let dir: String = TempDir::new().unwrap().path().to_str().unwrap().to_owned(); + let capabilities = "staking".to_string(); + + // Init cache + let mut error_msg = UnmanagedVector::default(); + let config = Config::new(CacheOptions::new( + dir, + [capabilities], + Size::mebi(512), + Size::mebi(32), + )); + let config = serde_json::to_vec(&config).unwrap(); + let cache_ptr = init_cache(ByteSliceView::new(config.as_slice()), Some(&mut error_msg)); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + // Get metrics 1 + let mut error_msg = UnmanagedVector::default(); + let metrics = get_metrics(cache_ptr, Some(&mut error_msg)); + let _ = error_msg.consume(); + assert_eq!(metrics, Metrics::default()); + + // Save wasm + let mut error_msg = UnmanagedVector::default(); + let checksum_hackatom = store_code( + cache_ptr, + ByteSliceView::new(HACKATOM), + false, + true, + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + let checksum = checksum_hackatom.consume().unwrap_or_default(); + + // Get metrics 2 + let mut error_msg = UnmanagedVector::default(); + let metrics = get_metrics(cache_ptr, Some(&mut error_msg)); + let _ = error_msg.consume(); + assert_eq!(metrics, Metrics::default()); + + // Pin + let mut error_msg = UnmanagedVector::default(); + pin( + cache_ptr, + ByteSliceView::new(&checksum), + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + // Get metrics 3 + let mut error_msg = UnmanagedVector::default(); + let metrics = get_metrics(cache_ptr, Some(&mut error_msg)); + let _ = error_msg.consume(); + let Metrics { + hits_pinned_memory_cache, + hits_memory_cache, + hits_fs_cache, + misses, + elements_pinned_memory_cache, + elements_memory_cache, + size_pinned_memory_cache, + size_memory_cache, + } = metrics; + assert_eq!(hits_pinned_memory_cache, 0); + assert_eq!(hits_memory_cache, 0); + assert_eq!(hits_fs_cache, 1); + assert_eq!(misses, 0); + assert_eq!(elements_pinned_memory_cache, 1); + assert_eq!(elements_memory_cache, 0); + assert_approx_eq!( + size_pinned_memory_cache, + 3400000, + "0.2", + "size_pinned_memory_cache: {size_pinned_memory_cache}" + ); + assert_eq!(size_memory_cache, 0); + + // Unpin + let mut error_msg = UnmanagedVector::default(); + unpin( + cache_ptr, + ByteSliceView::new(&checksum), + Some(&mut error_msg), + ); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + // Get metrics 4 + let mut error_msg = UnmanagedVector::default(); + let metrics = get_metrics(cache_ptr, Some(&mut error_msg)); + let _ = error_msg.consume(); + assert_eq!( + metrics, + Metrics { + hits_pinned_memory_cache: 0, + hits_memory_cache: 0, + hits_fs_cache: 1, + misses: 0, + elements_pinned_memory_cache: 0, + elements_memory_cache: 0, + size_pinned_memory_cache: 0, + size_memory_cache: 0, + } + ); + + release_cache(cache_ptr); + } + + #[test] + fn test_config_json() { + // see companion test "TestConfigJSON" on the Go side + const JSON: &str = r#"{"wasm_limits":{"initial_memory_limit_pages":15,"table_size_limit_elements":20,"max_imports":100,"max_function_params":0},"cache":{"base_dir":"/tmp","available_capabilities":["a","b"],"memory_cache_size_bytes":100,"instance_memory_limit_bytes":100}}"#; + + let config: Config = serde_json::from_str(JSON).unwrap(); + + assert_eq!(config.wasm_limits.initial_memory_limit_pages(), 15); + assert_eq!(config.wasm_limits.table_size_limit_elements(), 20); + assert_eq!(config.wasm_limits.max_imports(), 100); + assert_eq!(config.wasm_limits.max_function_params(), 0); + + // unset values have default values + assert_eq!(config.wasm_limits.max_total_function_params(), 10_000); + + assert_eq!(config.cache.base_dir, PathBuf::from("/tmp")); + assert_eq!( + config.cache.available_capabilities, + HashSet::from_iter(["a".to_string(), "b".to_string()]) + ); + assert_eq!(config.cache.memory_cache_size_bytes, Size::new(100)); + assert_eq!(config.cache.instance_memory_limit_bytes, Size::new(100)); + } +} diff --git a/libwasmvm/src/calls.rs b/libwasmvm/src/calls.rs new file mode 100644 index 000000000..b49ac142a --- /dev/null +++ b/libwasmvm/src/calls.rs @@ -0,0 +1,680 @@ +//! A module containing calls into smart contracts via Cache and Instance. + +use std::convert::TryInto; +use std::panic::{catch_unwind, AssertUnwindSafe}; +use std::time::SystemTime; +use time::{format_description::well_known::Rfc3339, OffsetDateTime}; + +use cosmwasm_std::Checksum; +use cosmwasm_vm::{ + call_execute_raw, call_ibc_channel_close_raw, call_ibc_channel_connect_raw, + call_ibc_channel_open_raw, call_ibc_destination_callback_raw, call_ibc_packet_ack_raw, + call_ibc_packet_receive_raw, call_ibc_packet_timeout_raw, call_ibc_source_callback_raw, + call_instantiate_raw, call_migrate_raw, call_migrate_with_info_raw, call_query_raw, + call_reply_raw, call_sudo_raw, Backend, Cache, Instance, InstanceOptions, VmResult, +}; + +use crate::api::GoApi; +use crate::args::{ARG1, ARG2, ARG3, CACHE_ARG, CHECKSUM_ARG, GAS_REPORT_ARG}; +use crate::cache::{cache_t, to_cache}; +use crate::db::Db; +use crate::error::{handle_c_error_binary, Error}; +use crate::handle_vm_panic::handle_vm_panic; +use crate::memory::{ByteSliceView, UnmanagedVector}; +use crate::querier::GoQuerier; +use crate::storage::GoStorage; +use crate::GasReport; + +fn into_backend(db: Db, api: GoApi, querier: GoQuerier) -> Backend { + Backend { + api, + storage: GoStorage::new(db), + querier, + } +} + +#[no_mangle] +pub extern "C" fn instantiate( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + info: ByteSliceView, + msg: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_3_args( + call_instantiate_raw, + cache, + checksum, + env, + info, + msg, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +#[no_mangle] +pub extern "C" fn execute( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + info: ByteSliceView, + msg: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_3_args( + call_execute_raw, + cache, + checksum, + env, + info, + msg, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +#[no_mangle] +pub extern "C" fn migrate( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_2_args( + call_migrate_raw, + cache, + checksum, + env, + msg, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +#[no_mangle] +pub extern "C" fn migrate_with_info( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + migrate_info: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_3_args( + call_migrate_with_info_raw, + cache, + checksum, + env, + msg, + migrate_info, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +#[no_mangle] +pub extern "C" fn sudo( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_2_args( + call_sudo_raw, + cache, + checksum, + env, + msg, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +#[no_mangle] +pub extern "C" fn reply( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_2_args( + call_reply_raw, + cache, + checksum, + env, + msg, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +#[no_mangle] +pub extern "C" fn query( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_2_args( + call_query_raw, + cache, + checksum, + env, + msg, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +#[no_mangle] +pub extern "C" fn ibc_channel_open( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_2_args( + call_ibc_channel_open_raw, + cache, + checksum, + env, + msg, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +#[no_mangle] +pub extern "C" fn ibc_channel_connect( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_2_args( + call_ibc_channel_connect_raw, + cache, + checksum, + env, + msg, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +#[no_mangle] +pub extern "C" fn ibc_channel_close( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_2_args( + call_ibc_channel_close_raw, + cache, + checksum, + env, + msg, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +#[no_mangle] +pub extern "C" fn ibc_packet_receive( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_2_args( + call_ibc_packet_receive_raw, + cache, + checksum, + env, + msg, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +#[no_mangle] +pub extern "C" fn ibc_packet_ack( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_2_args( + call_ibc_packet_ack_raw, + cache, + checksum, + env, + msg, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +#[no_mangle] +pub extern "C" fn ibc_packet_timeout( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_2_args( + call_ibc_packet_timeout_raw, + cache, + checksum, + env, + msg, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +#[no_mangle] +pub extern "C" fn ibc_source_callback( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_2_args( + call_ibc_source_callback_raw, + cache, + checksum, + env, + msg, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +#[no_mangle] +pub extern "C" fn ibc_destination_callback( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_2_args( + call_ibc_destination_callback_raw, + cache, + checksum, + env, + msg, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + +type VmFn2Args = fn( + instance: &mut Instance, + arg1: &[u8], + arg2: &[u8], +) -> VmResult>; + +// this wraps all error handling and ffi for the 6 ibc entry points and query. +// (all of which take env and one "msg" argument). +// the only difference is which low-level function they dispatch to. +fn call_2_args( + vm_fn: VmFn2Args, + cache: *mut cache_t, + checksum: ByteSliceView, + arg1: ByteSliceView, + arg2: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + let r = match to_cache(cache) { + Some(c) => catch_unwind(AssertUnwindSafe(move || { + do_call_2_args( + vm_fn, + c, + checksum, + arg1, + arg2, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + ) + })) + .unwrap_or_else(|err| { + handle_vm_panic("do_call_2_args", err); + Err(Error::panic()) + }), + None => Err(Error::unset_arg(CACHE_ARG)), + }; + let data = handle_c_error_binary(r, error_msg); + UnmanagedVector::new(Some(data)) +} + +// this is internal processing, same for all the 6 ibc entry points +fn do_call_2_args( + vm_fn: VmFn2Args, + cache: &mut Cache, + checksum: ByteSliceView, + arg1: ByteSliceView, + arg2: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, +) -> Result, Error> { + let gas_report = gas_report.ok_or_else(|| Error::empty_arg(GAS_REPORT_ARG))?; + let checksum: Checksum = checksum + .read() + .ok_or_else(|| Error::unset_arg(CHECKSUM_ARG))? + .try_into()?; + let arg1 = arg1.read().ok_or_else(|| Error::unset_arg(ARG1))?; + let arg2 = arg2.read().ok_or_else(|| Error::unset_arg(ARG2))?; + + let backend = into_backend(db, api, querier); + let options = InstanceOptions { gas_limit }; + let mut instance: Instance = + cache.get_instance(&checksum, backend, options)?; + + // If print_debug = false, use default debug handler from cosmwasm-vm, which discards messages + if print_debug { + instance.set_debug_handler(|msg, info| { + let t = now_rfc3339(); + let gas = info.gas_remaining; + eprintln!("[{t}]: {msg} (gas remaining: {gas})"); + }); + } + + // We only check this result after reporting gas usage and returning the instance into the cache. + let res = vm_fn(&mut instance, arg1, arg2); + *gas_report = instance.create_gas_report().into(); + Ok(res?) +} + +type VmFn3Args = fn( + instance: &mut Instance, + arg1: &[u8], + arg2: &[u8], + arg3: &[u8], +) -> VmResult>; + +// This wraps all error handling and ffi for instantiate, execute and migrate +// (and anything else that takes env, info and msg arguments). +// The only difference is which low-level function they dispatch to. +fn call_3_args( + vm_fn: VmFn3Args, + cache: *mut cache_t, + checksum: ByteSliceView, + arg1: ByteSliceView, + arg2: ByteSliceView, + arg3: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + let r = match to_cache(cache) { + Some(c) => catch_unwind(AssertUnwindSafe(move || { + do_call_3_args( + vm_fn, + c, + checksum, + arg1, + arg2, + arg3, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + ) + })) + .unwrap_or_else(|err| { + handle_vm_panic("do_call_3_args", err); + Err(Error::panic()) + }), + None => Err(Error::unset_arg(CACHE_ARG)), + }; + let data = handle_c_error_binary(r, error_msg); + UnmanagedVector::new(Some(data)) +} + +fn do_call_3_args( + vm_fn: VmFn3Args, + cache: &mut Cache, + checksum: ByteSliceView, + arg1: ByteSliceView, + arg2: ByteSliceView, + arg3: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, +) -> Result, Error> { + let gas_report = gas_report.ok_or_else(|| Error::empty_arg(GAS_REPORT_ARG))?; + let checksum: Checksum = checksum + .read() + .ok_or_else(|| Error::unset_arg(CHECKSUM_ARG))? + .try_into()?; + let arg1 = arg1.read().ok_or_else(|| Error::unset_arg(ARG1))?; + let arg2 = arg2.read().ok_or_else(|| Error::unset_arg(ARG2))?; + let arg3 = arg3.read().ok_or_else(|| Error::unset_arg(ARG3))?; + + let backend = into_backend(db, api, querier); + let options = InstanceOptions { gas_limit }; + let mut instance = cache.get_instance(&checksum, backend, options)?; + + // If print_debug = false, use default debug handler from cosmwasm-vm, which discards messages + if print_debug { + instance.set_debug_handler(|msg, info| { + let t = now_rfc3339(); + let gas = info.gas_remaining; + eprintln!("[{t}]: {msg} (gas remaining: {gas})"); + }); + } + + // We only check this result after reporting gas usage and returning the instance into the cache. + let res = vm_fn(&mut instance, arg1, arg2, arg3); + *gas_report = instance.create_gas_report().into(); + Ok(res?) +} + +fn now_rfc3339() -> String { + let dt = OffsetDateTime::from(SystemTime::now()); + dt.format(&Rfc3339).unwrap_or_default() +} diff --git a/libwasmvm/src/db.rs b/libwasmvm/src/db.rs new file mode 100644 index 000000000..3f75d8642 --- /dev/null +++ b/libwasmvm/src/db.rs @@ -0,0 +1,70 @@ +use crate::gas_meter::gas_meter_t; +use crate::iterator::GoIter; +use crate::memory::{U8SliceView, UnmanagedVector}; +use crate::vtables::Vtable; + +// this represents something passed in from the caller side of FFI +#[repr(C)] +pub struct db_t { + _private: [u8; 0], +} + +// These functions should return GoError but because we don't trust them here, we treat the return value as i32 +// and then check it when converting to GoError manually +#[repr(C)] +#[derive(Default)] +pub struct DbVtable { + pub read_db: Option< + extern "C" fn( + db: *mut db_t, + gas_meter: *mut gas_meter_t, + gas_used: *mut u64, + key: U8SliceView, + value_out: *mut UnmanagedVector, + err_msg_out: *mut UnmanagedVector, + ) -> i32, + >, + pub write_db: Option< + extern "C" fn( + db: *mut db_t, + gas_meter: *mut gas_meter_t, + gas_used: *mut u64, + key: U8SliceView, + value: U8SliceView, + err_msg_out: *mut UnmanagedVector, + ) -> i32, + >, + pub remove_db: Option< + extern "C" fn( + db: *mut db_t, + gas_meter: *mut gas_meter_t, + gas_used: *mut u64, + key: U8SliceView, + err_msg_out: *mut UnmanagedVector, + ) -> i32, + >, + // order -> Ascending = 1, Descending = 2 + // Note: we cannot set gas_meter on the returned GoIter due to cgo memory safety. + // Since we have the pointer in rust already, we must set that manually + pub scan_db: Option< + extern "C" fn( + db: *mut db_t, + gas_meter: *mut gas_meter_t, + gas_used: *mut u64, + start: U8SliceView, + end: U8SliceView, + order: i32, + iterator_out: *mut GoIter, + err_msg_out: *mut UnmanagedVector, + ) -> i32, + >, +} + +impl Vtable for DbVtable {} + +#[repr(C)] +pub struct Db { + pub gas_meter: *mut gas_meter_t, + pub state: *mut db_t, + pub vtable: DbVtable, +} diff --git a/libwasmvm/src/error/go.rs b/libwasmvm/src/error/go.rs new file mode 100644 index 000000000..1d8d65edd --- /dev/null +++ b/libwasmvm/src/error/go.rs @@ -0,0 +1,191 @@ +use cosmwasm_vm::BackendError; + +use crate::memory::UnmanagedVector; + +/// This enum gives names to the status codes returned from Go callbacks to Rust. +/// The Go code will return one of these variants when returning. +/// +/// 0 means no error, all the other cases are some sort of error. +/// +/// cbindgen:prefix-with-name +// NOTE TO DEVS: If you change the values assigned to the variants of this enum, You must also +// update the match statement in the From conversion below. +// Otherwise all hell may break loose. +// You have been warned. +// +#[repr(i32)] // This makes it so the enum looks like a simple i32 to Go +#[derive(PartialEq, Eq)] +pub enum GoError { + None = 0, + /// Go panicked for an unexpected reason. + Panic = 1, + /// Go received a bad argument from Rust + BadArgument = 2, + /// Ran out of gas while using the SDK (e.g. storage). This can come from the Cosmos SDK gas meter + /// (https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/store/types/gas.go#L29-L32). + OutOfGas = 3, + /// Error while trying to serialize data in Go code (typically json.Marshal) + CannotSerialize = 4, + /// An error happened during normal operation of a Go callback, which should be fed back to the contract + User = 5, + /// An error type that should never be created by us. It only serves as a fallback for the i32 to GoError conversion. + Other = -1, +} + +impl From for GoError { + fn from(n: i32) -> Self { + // This conversion treats any number that is not otherwise an expected value as `GoError::Other` + match n { + 0 => GoError::None, + 1 => GoError::Panic, + 2 => GoError::BadArgument, + 3 => GoError::OutOfGas, + 4 => GoError::CannotSerialize, + 5 => GoError::User, + _ => GoError::Other, + } + } +} + +impl GoError { + /// This converts a GoError to a `Result<(), BackendError>`, using a fallback error message for some cases. + /// If it is GoError::User the error message will be returned to the contract. + /// Otherwise, the returned error will trigger a trap in the VM and abort contract execution immediately. + /// + /// This reads data from an externally provided `UnmanagedVector` and assumes UFT-8 encoding. To protect + /// against invalid UTF-8 data, a lossy conversion to string is used. The data is limited to 8KB in order + /// to protect against long externally generated error messages. + /// + /// The `error_msg` is always consumed here and must not be used afterwards. + pub unsafe fn into_result( + self, + error_msg: UnmanagedVector, + default_error_msg: F, + ) -> Result<(), BackendError> + where + F: FnOnce() -> String, + { + const MAX_ERROR_LEN: usize = 8 * 1024; + + // We destruct the UnmanagedVector here, no matter if we need the data. + let error_msg = error_msg.consume(); + + let build_error_msg = || -> String { + match error_msg { + Some(mut data) => { + data.truncate(MAX_ERROR_LEN); + String::from_utf8_lossy(&data).into() + } + None => default_error_msg(), + } + }; + + match self { + // Success + GoError::None => Ok(()), + // Errors with direct counterpart + GoError::Panic => Err(BackendError::foreign_panic()), + GoError::BadArgument => Err(BackendError::bad_argument()), + GoError::OutOfGas => Err(BackendError::out_of_gas()), + GoError::User => Err(BackendError::user_err(build_error_msg())), + // Everything else goes into unknown + GoError::CannotSerialize | GoError::Other => { + Err(BackendError::unknown(build_error_msg())) + } + } + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_vm::BackendError; + + use super::{GoError, UnmanagedVector}; + + #[test] + fn go_error_into_result_works() { + let default = || "Something went wrong but we don't know".to_string(); + + let error = GoError::None; + let error_msg = UnmanagedVector::new(None); + let a = unsafe { error.into_result(error_msg, default) }; + assert_eq!(a, Ok(())); + + let error = GoError::Panic; + let error_msg = UnmanagedVector::new(None); + let a = unsafe { error.into_result(error_msg, default) }; + assert_eq!(a.unwrap_err(), BackendError::ForeignPanic {}); + + let error = GoError::BadArgument; + let error_msg = UnmanagedVector::new(None); + let a = unsafe { error.into_result(error_msg, default) }; + assert_eq!(a.unwrap_err(), BackendError::BadArgument {}); + + let error = GoError::OutOfGas; + let error_msg = UnmanagedVector::new(None); + let a = unsafe { error.into_result(error_msg, default) }; + assert_eq!(a.unwrap_err(), BackendError::OutOfGas {}); + + // CannotSerialize maps to Unknown + let error = GoError::CannotSerialize; + let error_msg = UnmanagedVector::new(None); + let a = unsafe { error.into_result(error_msg, default) }; + assert_eq!(a.unwrap_err(), BackendError::Unknown { msg: default() }); + + // GoError::User with none message + let error = GoError::User; + let error_msg = UnmanagedVector::new(None); + let a = unsafe { error.into_result(error_msg, default) }; + assert_eq!(a.unwrap_err(), BackendError::UserErr { msg: default() }); + + // GoError::User with some message + let error = GoError::User; + let error_msg = UnmanagedVector::new(Some(Vec::from(b"kaputt" as &[u8]))); + let a = unsafe { error.into_result(error_msg, default) }; + assert_eq!( + a.unwrap_err(), + BackendError::UserErr { + msg: "kaputt".to_string() + } + ); + + // GoError::User with some message too long message + let error = GoError::User; + let error_msg = UnmanagedVector::new(Some(vec![0x61; 10000])); // 10000 times "a" + let a = unsafe { error.into_result(error_msg, default) }; + assert_eq!( + a.unwrap_err(), + BackendError::UserErr { + msg: "a".repeat(8192) + } + ); + + // GoError::Other with none message + let error = GoError::Other; + let error_msg = UnmanagedVector::new(None); + let a = unsafe { error.into_result(error_msg, default) }; + assert_eq!(a.unwrap_err(), BackendError::Unknown { msg: default() }); + + // GoError::Other with some message + let error = GoError::Other; + let error_msg = UnmanagedVector::new(Some(Vec::from(b"kaputt" as &[u8]))); + let a = unsafe { error.into_result(error_msg, default) }; + assert_eq!( + a.unwrap_err(), + BackendError::Unknown { + msg: "kaputt".to_string() + } + ); + + // GoError::Other with some message too long message + let error = GoError::Other; + let error_msg = UnmanagedVector::new(Some(vec![0x61; 10000])); // 10000 times "a" + let a = unsafe { error.into_result(error_msg, default) }; + assert_eq!( + a.unwrap_err(), + BackendError::Unknown { + msg: "a".repeat(8192) + } + ); + } +} diff --git a/libwasmvm/src/error/mod.rs b/libwasmvm/src/error/mod.rs new file mode 100644 index 000000000..96130ed83 --- /dev/null +++ b/libwasmvm/src/error/mod.rs @@ -0,0 +1,7 @@ +mod go; +mod rust; + +pub use go::GoError; +pub use rust::{ + handle_c_error_binary, handle_c_error_default, handle_c_error_ptr, RustError as Error, +}; diff --git a/libwasmvm/src/error/rust.rs b/libwasmvm/src/error/rust.rs new file mode 100644 index 000000000..5c99e185c --- /dev/null +++ b/libwasmvm/src/error/rust.rs @@ -0,0 +1,585 @@ +use cosmwasm_std::ChecksumError; +use cosmwasm_vm::VmError; +use errno::{set_errno, Errno}; +#[cfg(feature = "backtraces")] +use std::backtrace::Backtrace; +use thiserror::Error; + +use crate::memory::UnmanagedVector; + +#[derive(Error, Debug)] +pub enum RustError { + #[error("Empty argument: {}", name)] + EmptyArg { + name: String, + #[cfg(feature = "backtraces")] + backtrace: Backtrace, + }, + /// Whenever UTF-8 bytes cannot be decoded into a unicode string, e.g. in String::from_utf8 or str::from_utf8. + #[error("Cannot decode UTF8 bytes into string: {}", msg)] + InvalidUtf8 { + msg: String, + #[cfg(feature = "backtraces")] + backtrace: Backtrace, + }, + #[error("Cannot deserialize from MessagePack: {msg}")] + MessagePack { + msg: String, + #[cfg(feature = "backtraces")] + backtrace: Backtrace, + }, + #[error("Cannot serialize to / deserialize from JSON: {msg}")] + Json { + msg: String, + #[cfg(feature = "backtraces")] + backtrace: Backtrace, + }, + #[error("Ran out of gas")] + OutOfGas { + #[cfg(feature = "backtraces")] + backtrace: Backtrace, + }, + #[error("Caught panic")] + Panic { + #[cfg(feature = "backtraces")] + backtrace: Backtrace, + }, + #[error("Null/Nil argument: {}", name)] + UnsetArg { + name: String, + #[cfg(feature = "backtraces")] + backtrace: Backtrace, + }, + #[error("Checksum not of length 32")] + ChecksumError { + #[cfg(feature = "backtraces")] + backtrace: Backtrace, + }, + #[error("Error calling the VM: {}", msg)] + VmErr { + msg: String, + #[cfg(feature = "backtraces")] + backtrace: Backtrace, + }, +} + +impl RustError { + pub fn empty_arg>(name: T) -> Self { + RustError::EmptyArg { + name: name.into(), + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), + } + } + + pub fn invalid_utf8(msg: S) -> Self { + RustError::InvalidUtf8 { + msg: msg.to_string(), + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), + } + } + + pub fn message_pack(msg: S) -> Self { + RustError::MessagePack { + msg: msg.to_string(), + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), + } + } + + pub fn panic() -> Self { + RustError::Panic { + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), + } + } + + pub fn unset_arg>(name: T) -> Self { + RustError::UnsetArg { + name: name.into(), + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), + } + } + + pub fn vm_err(msg: S) -> Self { + RustError::VmErr { + msg: msg.to_string(), + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), + } + } + + pub fn out_of_gas() -> Self { + RustError::OutOfGas { + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), + } + } + + pub fn checksum_err() -> Self { + RustError::ChecksumError { + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), + } + } +} + +impl From for RustError { + fn from(source: VmError) -> Self { + match source { + VmError::GasDepletion { .. } => RustError::out_of_gas(), + _ => RustError::vm_err(source), + } + } +} + +impl From for RustError { + fn from(_: ChecksumError) -> Self { + RustError::checksum_err() + } +} + +impl From for RustError { + fn from(source: serde_json::Error) -> Self { + RustError::Json { + msg: source.to_string(), + #[cfg(feature = "backtraces")] + backtrace: Backtrace::capture(), + } + } +} + +impl From for RustError { + fn from(source: rmp_serde::encode::Error) -> Self { + RustError::message_pack(source) + } +} + +impl From for RustError { + fn from(source: rmp_serde::decode::Error) -> Self { + RustError::message_pack(source) + } +} + +impl From for RustError { + fn from(source: std::str::Utf8Error) -> Self { + RustError::invalid_utf8(source) + } +} + +impl From for RustError { + fn from(source: std::string::FromUtf8Error) -> Self { + RustError::invalid_utf8(source) + } +} + +/// cbindgen:prefix-with-name +#[repr(i32)] +enum ErrnoValue { + Success = 0, + Other = 1, + OutOfGas = 2, +} + +pub fn clear_error() { + set_errno(Errno(ErrnoValue::Success as i32)); +} + +pub fn set_error(err: RustError, error_msg: Option<&mut UnmanagedVector>) { + if let Some(error_msg) = error_msg { + let msg: Vec = err.to_string().into(); + *error_msg = UnmanagedVector::new(Some(msg)); + } else { + // The caller provided a nil pointer for the error message. + // That's not nice but we can live with it. + } + + let errno = match err { + RustError::OutOfGas { .. } => ErrnoValue::OutOfGas, + _ => ErrnoValue::Other, + } as i32; + set_errno(Errno(errno)); +} + +/// If `result` is Ok, this returns the Ok value and clears [errno]. +/// Otherwise it returns a null pointer, writes the error message to `error_msg` and sets [errno]. +/// +/// [errno]: https://utcc.utoronto.ca/~cks/space/blog/programming/GoCgoErrorReturns +pub fn handle_c_error_ptr( + result: Result<*mut T, RustError>, + error_msg: Option<&mut UnmanagedVector>, +) -> *mut T { + match result { + Ok(value) => { + clear_error(); + value + } + Err(error) => { + set_error(error, error_msg); + std::ptr::null_mut() + } + } +} + +/// If `result` is Ok, this returns the binary representation of the Ok value and clears [errno]. +/// Otherwise it returns an empty vector, writes the error message to `error_msg` and sets [errno]. +/// +/// [errno]: https://utcc.utoronto.ca/~cks/space/blog/programming/GoCgoErrorReturns +pub fn handle_c_error_binary( + result: Result, + error_msg: Option<&mut UnmanagedVector>, +) -> Vec +where + T: Into>, +{ + match result { + Ok(value) => { + clear_error(); + value.into() + } + Err(error) => { + set_error(error, error_msg); + Vec::new() + } + } +} + +/// If `result` is Ok, this returns the Ok value and clears [errno]. +/// Otherwise it returns the default value, writes the error message to `error_msg` and sets [errno]. +/// +/// [errno]: https://utcc.utoronto.ca/~cks/space/blog/programming/GoCgoErrorReturns +pub fn handle_c_error_default( + result: Result, + error_msg: Option<&mut UnmanagedVector>, +) -> T +where + T: Default, +{ + match result { + Ok(value) => { + clear_error(); + value + } + Err(error) => { + set_error(error, error_msg); + Default::default() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::Checksum; + use cosmwasm_vm::BackendError; + use errno::errno; + use std::str; + + #[test] + fn empty_arg_works() { + let error = RustError::empty_arg("gas"); + match error { + RustError::EmptyArg { name, .. } => { + assert_eq!(name, "gas"); + } + _ => panic!("expect different error"), + } + } + + #[test] + fn invalid_utf8_works_for_strings() { + let error = RustError::invalid_utf8("my text"); + match error { + RustError::InvalidUtf8 { msg, .. } => { + assert_eq!(msg, "my text"); + } + _ => panic!("expect different error"), + } + } + + #[test] + fn invalid_utf8_works_for_errors() { + let original = String::from_utf8(vec![0x80]).unwrap_err(); + let error = RustError::invalid_utf8(original); + match error { + RustError::InvalidUtf8 { msg, .. } => { + assert_eq!(msg, "invalid utf-8 sequence of 1 bytes from index 0"); + } + _ => panic!("expect different error"), + } + } + + #[test] + fn panic_works() { + let error = RustError::panic(); + match error { + RustError::Panic { .. } => {} + _ => panic!("expect different error"), + } + } + + #[test] + fn unset_arg_works() { + let error = RustError::unset_arg("gas"); + match error { + RustError::UnsetArg { name, .. } => { + assert_eq!(name, "gas"); + } + _ => panic!("expect different error"), + } + } + + #[test] + fn vm_err_works_for_strings() { + let error = RustError::vm_err("my text"); + match error { + RustError::VmErr { msg, .. } => { + assert_eq!(msg, "my text"); + } + _ => panic!("expect different error"), + } + } + + #[test] + fn vm_err_works_for_errors() { + // No public interface exists to generate a VmError directly + let original: VmError = BackendError::out_of_gas().into(); + let error = RustError::vm_err(original); + match error { + RustError::VmErr { msg, .. } => { + assert_eq!(msg, "Ran out of gas during contract execution"); + } + _ => panic!("expect different error"), + } + } + + // Tests of `impl From for RustError` converters + + #[test] + fn from_std_str_utf8error_works() { + let broken = Vec::from(b"Hello \xF0\x90\x80World" as &[u8]); + let error: RustError = str::from_utf8(&broken).unwrap_err().into(); + match error { + RustError::InvalidUtf8 { msg, .. } => { + assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6") + } + _ => panic!("expect different error"), + } + } + + #[test] + fn from_std_string_fromutf8error_works() { + let error: RustError = String::from_utf8(b"Hello \xF0\x90\x80World".to_vec()) + .unwrap_err() + .into(); + match error { + RustError::InvalidUtf8 { msg, .. } => { + assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6") + } + _ => panic!("expect different error"), + } + } + + #[test] + fn handle_c_error_binary_works() { + // Ok (non-empty vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Ok(vec![0xF0, 0x0B, 0xAA]); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, vec![0xF0, 0x0B, 0xAA]); + let _ = error_msg.consume(); + + // Ok (empty vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Ok(vec![]); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Ok (non-empty slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], RustError> = Ok(b"foobar"); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::from(b"foobar" as &[u8])); + let _ = error_msg.consume(); + + // Ok (empty slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], RustError> = Ok(b""); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Ok (checksum) + let mut error_msg = UnmanagedVector::default(); + let res: Result = Ok(Checksum::from([ + 0x72, 0x2c, 0x8c, 0x99, 0x3f, 0xd7, 0x5a, 0x76, 0x27, 0xd6, 0x9e, 0xd9, 0x41, 0x34, + 0x4f, 0xe2, 0xa1, 0x42, 0x3a, 0x3e, 0x75, 0xef, 0xd3, 0xe6, 0x77, 0x8a, 0x14, 0x28, + 0x84, 0x22, 0x71, 0x04, + ])); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!( + data, + vec![ + 0x72, 0x2c, 0x8c, 0x99, 0x3f, 0xd7, 0x5a, 0x76, 0x27, 0xd6, 0x9e, 0xd9, 0x41, 0x34, + 0x4f, 0xe2, 0xa1, 0x42, 0x3a, 0x3e, 0x75, 0xef, 0xd3, 0xe6, 0x77, 0x8a, 0x14, 0x28, + 0x84, 0x22, 0x71, 0x04, + ] + ); + let _ = error_msg.consume(); + + // Err (vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Err(RustError::panic()); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Err (slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], RustError> = Err(RustError::panic()); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Err (checksum) + let mut error_msg = UnmanagedVector::default(); + let res: Result = Err(RustError::panic()); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + } + + #[test] + fn handle_c_error_binary_clears_an_old_error() { + // Err + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Err(RustError::panic()); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Ok + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Ok(vec![0xF0, 0x0B, 0xAA]); + let data = handle_c_error_binary(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, vec![0xF0, 0x0B, 0xAA]); + let _ = error_msg.consume(); + } + + #[test] + fn handle_c_error_default_works() { + // Ok (non-empty vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Ok(vec![0xF0, 0x0B, 0xAA]); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, vec![0xF0, 0x0B, 0xAA]); + let _ = error_msg.consume(); + + // Ok (empty vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Ok(vec![]); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Ok (non-empty slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], RustError> = Ok(b"foobar"); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::from(b"foobar" as &[u8])); + let _ = error_msg.consume(); + + // Ok (empty slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], RustError> = Ok(b""); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Ok (unit) + let mut error_msg = UnmanagedVector::default(); + let res: Result<(), RustError> = Ok(()); + handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + let _ = error_msg.consume(); + + // Err (vector) + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Err(RustError::panic()); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Err (slice) + let mut error_msg = UnmanagedVector::default(); + let res: Result<&[u8], RustError> = Err(RustError::panic()); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Err (unit) + let mut error_msg = UnmanagedVector::default(); + let res: Result<(), RustError> = Err(RustError::panic()); + handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + let _ = error_msg.consume(); + } + + #[test] + fn handle_c_error_default_clears_an_old_error() { + // Err + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Err(RustError::panic()); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Other as i32); + assert!(error_msg.is_some()); + assert_eq!(data, Vec::::new()); + let _ = error_msg.consume(); + + // Ok + let mut error_msg = UnmanagedVector::default(); + let res: Result, RustError> = Ok(vec![0xF0, 0x0B, 0xAA]); + let data = handle_c_error_default(res, Some(&mut error_msg)); + assert_eq!(errno().0, ErrnoValue::Success as i32); + assert!(error_msg.is_none()); + assert_eq!(data, vec![0xF0, 0x0B, 0xAA]); + let _ = error_msg.consume(); + } +} diff --git a/libwasmvm/src/examples/wasmvmstatic.rs b/libwasmvm/src/examples/wasmvmstatic.rs new file mode 100644 index 000000000..441dfda88 --- /dev/null +++ b/libwasmvm/src/examples/wasmvmstatic.rs @@ -0,0 +1,5 @@ +// This is an entry point into the library in order to set +// crate-type = ["staticlib"] via a command line argument. +// See `--example wasmvmstatic` + +pub use wasmvm::*; diff --git a/libwasmvm/src/gas_meter.rs b/libwasmvm/src/gas_meter.rs new file mode 100644 index 000000000..37291399a --- /dev/null +++ b/libwasmvm/src/gas_meter.rs @@ -0,0 +1,5 @@ +/// An opaque type. `*gas_meter_t` represents a pointer to Go memory holding the gas meter. +#[repr(C)] +pub struct gas_meter_t { + _private: [u8; 0], +} diff --git a/libwasmvm/src/gas_report.rs b/libwasmvm/src/gas_report.rs new file mode 100644 index 000000000..1f9e80ad6 --- /dev/null +++ b/libwasmvm/src/gas_report.rs @@ -0,0 +1,31 @@ +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct GasReport { + /// The original limit the instance was created with + pub limit: u64, + /// The remaining gas that can be spend + pub remaining: u64, + /// The amount of gas that was spend and metered externally in operations triggered by this instance + pub used_externally: u64, + /// The amount of gas that was spend and metered internally (i.e. by executing Wasm and calling + /// API methods which are not metered externally) + pub used_internally: u64, +} + +impl From for GasReport { + fn from( + cosmwasm_vm::GasReport { + limit, + remaining, + used_externally, + used_internally, + }: cosmwasm_vm::GasReport, + ) -> Self { + Self { + limit, + remaining, + used_externally, + used_internally, + } + } +} diff --git a/libwasmvm/src/handle_vm_panic.rs b/libwasmvm/src/handle_vm_panic.rs new file mode 100644 index 000000000..0eb0dca9d --- /dev/null +++ b/libwasmvm/src/handle_vm_panic.rs @@ -0,0 +1,34 @@ +use std::any::Any; + +/// A function to process cases in which the VM panics. +/// +/// We want to provide as much debug information as possible +/// as those cases are not expated to happen during healthy operations. +pub fn handle_vm_panic(what: &str, err: Box) { + eprintln!("Panic in {what}:"); + eprintln!("{err:?}"); // Does not show useful information, see https://users.rust-lang.org/t/return-value-from-catch-unwind-is-a-useless-any/89134/6 + eprintln!( + "This indicates a panic in during the operations of libwasmvm/cosmwasm-vm. +Such panics must not happen and are considered bugs. If you see this in any real-world or +close-to-real-world usage of wasmvm, please consider filing a security report, +no matter if it can be abused or not: +(https://github.com/CosmWasm/advisories/blob/main/SECURITY.md#reporting-a-vulnerability). +Thank you for your help keeping CosmWasm safe and secure 💚" + ); +} + +#[cfg(test)] +mod tests { + use super::*; + + use std::panic::catch_unwind; + + #[test] + fn handle_vm_panic_works() { + fn nice_try() { + panic!("oh no!"); + } + let err = catch_unwind(nice_try).unwrap_err(); + handle_vm_panic("nice_try", err); + } +} diff --git a/libwasmvm/src/iterator.rs b/libwasmvm/src/iterator.rs new file mode 100644 index 000000000..def6751fe --- /dev/null +++ b/libwasmvm/src/iterator.rs @@ -0,0 +1,203 @@ +use cosmwasm_std::Record; +use cosmwasm_vm::{BackendError, BackendResult, GasInfo}; + +use crate::error::GoError; +use crate::gas_meter::gas_meter_t; +use crate::memory::UnmanagedVector; +use crate::vtables::Vtable; + +/// A reference to some tables on the Go side which allow accessing +/// the actual iterator instance. +#[repr(C)] +#[derive(Default, Copy, Clone)] +pub struct IteratorReference { + /// An ID assigned to this contract call + pub call_id: u64, + /// An ID assigned to this iterator + pub iterator_id: u64, +} + +// These functions should return GoError but because we don't trust them here, we treat the return value as i32 +// and then check it when converting to GoError manually +#[repr(C)] +#[derive(Default)] +pub struct IteratorVtable { + pub next: Option< + extern "C" fn( + iterator: IteratorReference, + gas_meter: *mut gas_meter_t, + gas_used: *mut u64, + key_out: *mut UnmanagedVector, + value_out: *mut UnmanagedVector, + err_msg_out: *mut UnmanagedVector, + ) -> i32, + >, + pub next_key: Option< + extern "C" fn( + iterator: IteratorReference, + gas_meter: *mut gas_meter_t, + gas_used: *mut u64, + key_out: *mut UnmanagedVector, + err_msg_out: *mut UnmanagedVector, + ) -> i32, + >, + pub next_value: Option< + extern "C" fn( + iterator: IteratorReference, + gas_meter: *mut gas_meter_t, + gas_used: *mut u64, + value_out: *mut UnmanagedVector, + err_msg_out: *mut UnmanagedVector, + ) -> i32, + >, +} + +impl Vtable for IteratorVtable {} + +#[repr(C)] +pub struct GoIter { + pub gas_meter: *mut gas_meter_t, + /// A reference which identifies the iterator and allows finding and accessing the + /// actual iterator instance in Go. Once fully initialized, this is immutable. + pub reference: IteratorReference, + pub vtable: IteratorVtable, +} + +impl GoIter { + /// Creates an incomplete GoIter with unset fields. + /// This is not ready to be used until those fields are set. + /// + /// This is needed to create a correct instance in Rust + /// which is then filled in Go (see `fn scan`). + pub fn stub() -> Self { + GoIter { + reference: IteratorReference::default(), + gas_meter: std::ptr::null_mut(), + vtable: IteratorVtable::default(), + } + } + + pub fn next(&mut self) -> BackendResult> { + let next = self + .vtable + .next + .expect("iterator vtable function 'next' not set"); + + let mut output_key = UnmanagedVector::default(); + let mut output_value = UnmanagedVector::default(); + let mut error_msg = UnmanagedVector::default(); + let mut used_gas = 0_u64; + let go_result: GoError = (next)( + self.reference, + self.gas_meter, + &mut used_gas as *mut u64, + &mut output_key as *mut UnmanagedVector, + &mut output_value as *mut UnmanagedVector, + &mut error_msg as *mut UnmanagedVector, + ) + .into(); + // We destruct the `UnmanagedVector`s here, no matter if we need the data. + let output_key = output_key.consume(); + let output_value = output_value.consume(); + + let gas_info = GasInfo::with_externally_used(used_gas); + + // return complete error message (reading from buffer for GoError::Other) + let default = || "Failed to fetch next item from iterator".to_string(); + unsafe { + if let Err(err) = go_result.into_result(error_msg, default) { + return (Err(err), gas_info); + } + } + + let result = match output_key { + Some(key) => { + if let Some(value) = output_value { + Ok(Some((key, value))) + } else { + Err(BackendError::unknown( + "Failed to read value while reading the next key in the db", + )) + } + } + None => Ok(None), + }; + (result, gas_info) + } + + pub fn next_key(&mut self) -> BackendResult>> { + let next_key = self + .vtable + .next_key + .expect("iterator vtable function 'next_key' not set"); + self.next_key_or_val(next_key) + } + + pub fn next_value(&mut self) -> BackendResult>> { + let next_value = self + .vtable + .next_value + .expect("iterator vtable function 'next_value' not set"); + self.next_key_or_val(next_value) + } + + #[inline(always)] + fn next_key_or_val( + &mut self, + next: extern "C" fn( + iterator: IteratorReference, + gas_meter: *mut gas_meter_t, + gas_limit: *mut u64, + key_or_value_out: *mut UnmanagedVector, // key if called from next_key; value if called from next_value + err_msg_out: *mut UnmanagedVector, + ) -> i32, + ) -> BackendResult>> { + let mut output = UnmanagedVector::default(); + let mut error_msg = UnmanagedVector::default(); + let mut used_gas = 0_u64; + let go_result: GoError = (next)( + self.reference, + self.gas_meter, + &mut used_gas as *mut u64, + &mut output as *mut UnmanagedVector, + &mut error_msg as *mut UnmanagedVector, + ) + .into(); + // We destruct the `UnmanagedVector`s here, no matter if we need the data. + let output = output.consume(); + + let gas_info = GasInfo::with_externally_used(used_gas); + + // return complete error message (reading from buffer for GoError::Other) + let default = || "Failed to fetch next item from iterator".to_string(); + unsafe { + if let Err(err) = go_result.into_result(error_msg, default) { + return (Err(err), gas_info); + } + } + + (Ok(output), gas_info) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn goiter_stub_works() { + // can be created and dropped + { + let _iter = GoIter::stub(); + } + + // creates an all null-instance + let iter = GoIter::stub(); + assert!(iter.gas_meter.is_null()); + assert_eq!(iter.reference.call_id, 0); + assert_eq!(iter.reference.iterator_id, 0); + assert!(iter.vtable.next.is_none()); + assert!(iter.vtable.next_key.is_none()); + assert!(iter.vtable.next_value.is_none()); + } +} diff --git a/libwasmvm/src/lib.rs b/libwasmvm/src/lib.rs new file mode 100644 index 000000000..9db5cb79f --- /dev/null +++ b/libwasmvm/src/lib.rs @@ -0,0 +1,36 @@ +#![cfg_attr(feature = "backtraces", feature(backtrace))] +#![allow(clippy::not_unsafe_ptr_arg_deref, clippy::missing_safety_doc)] + +mod api; +mod args; +mod cache; +mod calls; +mod db; +mod error; +mod gas_meter; +mod gas_report; +mod handle_vm_panic; +mod iterator; +mod memory; +mod querier; +mod storage; +mod test_utils; +mod tests; +mod version; +mod vtables; + +// We only interact with this crate via `extern "C"` interfaces, not those public +// exports. There are no guarantees those exports are stable. +// We keep them here such that we can access them in the docs (`cargo doc`). +pub use api::{GoApi, GoApiVtable}; +pub use cache::{cache_t, load_wasm}; +pub use db::{db_t, Db, DbVtable}; +pub use error::GoError; +pub use gas_report::GasReport; +pub use iterator::IteratorVtable; +pub use memory::{ + destroy_unmanaged_vector, new_unmanaged_vector, ByteSliceView, U8SliceView, UnmanagedVector, +}; +pub use querier::{GoQuerier, QuerierVtable}; +pub use storage::GoStorage; +pub use vtables::Vtable; diff --git a/libwasmvm/src/memory.rs b/libwasmvm/src/memory.rs new file mode 100644 index 000000000..9483ec37b --- /dev/null +++ b/libwasmvm/src/memory.rs @@ -0,0 +1,470 @@ +use std::mem; +use std::slice; + +/// A view into an externally owned byte slice (Go `[]byte`). +/// Use this for the current call only. A view cannot be copied for safety reasons. +/// If you need a copy, use [`ByteSliceView::to_owned`]. +/// +/// Go's nil value is fully supported, such that we can differentiate between nil and an empty slice. +#[repr(C)] +#[derive(Debug)] +pub struct ByteSliceView { + /// True if and only if the byte slice is nil in Go. If this is true, the other fields must be ignored. + is_nil: bool, + ptr: *const u8, + len: usize, +} + +impl ByteSliceView { + /// ByteSliceViews are only constructed in Go. This constructor is a way to mimic the behaviour + /// when testing FFI calls from Rust. It must not be used in production code. + #[cfg(test)] + pub fn new(source: &[u8]) -> Self { + Self { + is_nil: false, + ptr: source.as_ptr(), + len: source.len(), + } + } + + /// ByteSliceViews are only constructed in Go. This constructor is a way to mimic the behaviour + /// when testing FFI calls from Rust. It must not be used in production code. + #[cfg(test)] + pub fn nil() -> Self { + Self { + is_nil: true, + ptr: std::ptr::null::(), + len: 0, + } + } + + /// Provides a reference to the included data to be parsed or copied elsewhere + /// This is safe as long as the `ByteSliceView` is constructed correctly. + pub fn read(&self) -> Option<&[u8]> { + if self.is_nil { + None + } else { + Some( + // "`data` must be non-null and aligned even for zero-length slices" + if self.len == 0 { + let dangling = std::ptr::NonNull::::dangling(); + unsafe { slice::from_raw_parts(dangling.as_ptr(), 0) } + } else { + unsafe { slice::from_raw_parts(self.ptr, self.len) } + }, + ) + } + } + + /// Creates an owned copy that can safely be stored and mutated. + #[allow(dead_code)] + pub fn to_owned(&self) -> Option> { + self.read().map(|slice| slice.to_owned()) + } +} + +/// A view into a `Option<&[u8]>`, created and maintained by Rust. +/// +/// This can be copied into a []byte in Go. +#[repr(C)] +pub struct U8SliceView { + /// True if and only if this is None. If this is true, the other fields must be ignored. + is_none: bool, + ptr: *const u8, + len: usize, +} + +impl U8SliceView { + pub fn new(source: Option<&[u8]>) -> Self { + match source { + Some(data) => Self { + is_none: false, + ptr: data.as_ptr(), + len: data.len(), + }, + None => Self { + is_none: true, + ptr: std::ptr::null::(), + len: 0, + }, + } + } +} + +/// An optional Vector type that requires explicit creation and destruction +/// and can be sent via FFI. +/// It can be created from `Option>` and be converted into `Option>`. +/// +/// This type is always created in Rust and always dropped in Rust. +/// If Go code want to create it, it must instruct Rust to do so via the +/// [`new_unmanaged_vector`] FFI export. If Go code wants to consume its data, +/// it must create a copy and instruct Rust to destroy it via the +/// [`destroy_unmanaged_vector`] FFI export. +/// +/// An UnmanagedVector is immutable. +/// +/// ## Ownership +/// +/// Ownership is the right and the obligation to destroy an `UnmanagedVector` +/// exactly once. Both Rust and Go can create an `UnmanagedVector`, which gives +/// then ownership. Sometimes it is necessary to transfer ownership. +/// +/// ### Transfer ownership from Rust to Go +/// +/// When an `UnmanagedVector` was created in Rust using [`UnmanagedVector::new`], [`UnmanagedVector::default`] +/// or [`new_unmanaged_vector`], it can be passed to Go as a return value (see e.g. [load_wasm][crate::load_wasm]). +/// Rust then has no chance to destroy the vector anymore, so ownership is transferred to Go. +/// In Go, the data has to be copied to a garbage collected `[]byte`. Then the vector must be destroyed +/// using [`destroy_unmanaged_vector`]. +/// +/// ### Transfer ownership from Go to Rust +/// +/// When Rust code calls into Go (using the vtable methods), return data or error messages must be created +/// in Go. This is done by calling [`new_unmanaged_vector`] from Go, which copies data into a newly created +/// `UnmanagedVector`. Since Go created it, it owns it. The ownership is then passed to Rust via the +/// mutable return value pointers. On the Rust side, the vector is destroyed using [`UnmanagedVector::consume`]. +/// +/// ## Examples +/// +/// Transferring ownership from Rust to Go using return values of FFI calls: +/// +/// ``` +/// # use wasmvm::{cache_t, ByteSliceView, UnmanagedVector}; +/// #[no_mangle] +/// pub extern "C" fn save_wasm_to_cache( +/// cache: *mut cache_t, +/// wasm: ByteSliceView, +/// error_msg: Option<&mut UnmanagedVector>, +/// ) -> UnmanagedVector { +/// # let checksum: Vec = Default::default(); +/// // some operation producing a `let checksum: Vec` +/// +/// UnmanagedVector::new(Some(checksum)) // this unmanaged vector is owned by the caller +/// } +/// ``` +/// +/// Transferring ownership from Go to Rust using return value pointers: +/// +/// ```rust +/// # use cosmwasm_vm::{BackendResult, GasInfo}; +/// # use wasmvm::{Db, GoError, U8SliceView, UnmanagedVector}; +/// fn db_read(db: &Db, key: &[u8]) -> BackendResult>> { +/// +/// // Create a None vector in order to reserve memory for the result +/// let mut output = UnmanagedVector::default(); +/// +/// // … +/// # let mut error_msg = UnmanagedVector::default(); +/// # let mut used_gas = 0_u64; +/// # let read_db = db.vtable.read_db.unwrap(); +/// +/// let go_error: GoError = read_db( +/// db.state, +/// db.gas_meter, +/// &mut used_gas as *mut u64, +/// U8SliceView::new(Some(key)), +/// // Go will create a new UnmanagedVector and override this address +/// &mut output as *mut UnmanagedVector, +/// &mut error_msg as *mut UnmanagedVector, +/// ) +/// .into(); +/// +/// // We now own the new UnmanagedVector written to the pointer and must destroy it +/// let value = output.consume(); +/// +/// // Some gas processing and error handling +/// # let gas_info = GasInfo::free(); +/// +/// (Ok(value), gas_info) +/// } +/// ``` +/// +/// +/// If you want to mutate data, you need to consume the vector and create a new one: +/// +/// ```rust +/// # use wasmvm::{UnmanagedVector}; +/// # let input = UnmanagedVector::new(Some(vec![0xAA])); +/// let mut mutable: Vec = input.consume().unwrap_or_default(); +/// assert_eq!(mutable, vec![0xAA]); +/// +/// // `input` is now gone and we cam do everything we want to `mutable`, +/// // including operations that reallocate the underlying data. +/// +/// mutable.push(0xBB); +/// mutable.push(0xCC); +/// +/// assert_eq!(mutable, vec![0xAA, 0xBB, 0xCC]); +/// +/// let output = UnmanagedVector::new(Some(mutable)); +/// +/// // `output` is ready to be passed around +/// ``` +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct UnmanagedVector { + /// True if and only if this is None. If this is true, the other fields must be ignored. + is_none: bool, + ptr: *mut u8, + len: usize, + cap: usize, +} + +impl UnmanagedVector { + /// Consumes this optional vector for manual management. + /// This is a zero-copy operation. + pub fn new(source: Option>) -> Self { + match source { + Some(data) => { + let (ptr, len, cap) = { + if data.capacity() == 0 { + // we need to explicitly use a null pointer here, since `as_mut_ptr` + // always returns a dangling pointer (e.g. 0x01) on an empty Vec, + // which trips up Go's pointer checks. + // This is safe because the Vec has not allocated, so no memory is leaked. + (std::ptr::null_mut::(), 0, 0) + } else { + // Can be replaced with Vec::into_raw_parts when stable + // https://doc.rust-lang.org/std/vec/struct.Vec.html#method.into_raw_parts + let mut data = mem::ManuallyDrop::new(data); + (data.as_mut_ptr(), data.len(), data.capacity()) + } + }; + Self { + is_none: false, + ptr, + len, + cap, + } + } + None => Self { + is_none: true, + ptr: std::ptr::null_mut::(), + len: 0, + cap: 0, + }, + } + } + + /// Creates a non-none UnmanagedVector with the given data. + pub fn some(data: impl Into>) -> Self { + Self::new(Some(data.into())) + } + + /// Creates a none UnmanagedVector. + pub fn none() -> Self { + Self::new(None) + } + + pub fn is_none(&self) -> bool { + self.is_none + } + + pub fn is_some(&self) -> bool { + !self.is_none() + } + + /// Takes this UnmanagedVector and turns it into a regular, managed Rust vector. + /// Calling this on two copies of UnmanagedVector leads to double free crashes. + pub fn consume(self) -> Option> { + if self.is_none { + None + } else if self.cap == 0 { + // capacity 0 means the vector was never allocated and + // the ptr field does not point to an actual byte buffer + // (we normalize to `null` in `UnmanagedVector::new`), + // so no memory is leaked by ignoring the ptr field here. + Some(Vec::new()) + } else { + Some(unsafe { Vec::from_raw_parts(self.ptr, self.len, self.cap) }) + } + } +} + +impl Default for UnmanagedVector { + fn default() -> Self { + Self::none() + } +} + +#[no_mangle] +pub extern "C" fn new_unmanaged_vector( + nil: bool, + ptr: *const u8, + length: usize, +) -> UnmanagedVector { + if nil { + UnmanagedVector::new(None) + } else if length == 0 { + UnmanagedVector::new(Some(Vec::new())) + } else { + // In slice::from_raw_parts, `data` must be non-null and aligned even for zero-length slices. + // For this reason we cover the length == 0 case separately above. + let external_memory = unsafe { slice::from_raw_parts(ptr, length) }; + let copy = Vec::from(external_memory); + UnmanagedVector::new(Some(copy)) + } +} + +#[no_mangle] +pub extern "C" fn destroy_unmanaged_vector(v: UnmanagedVector) { + let _ = v.consume(); +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn byte_slice_view_read_works() { + let data = vec![0xAA, 0xBB, 0xCC]; + let view = ByteSliceView::new(&data); + assert_eq!(view.read().unwrap(), &[0xAA, 0xBB, 0xCC]); + + let data = vec![]; + let view = ByteSliceView::new(&data); + assert_eq!(view.read().unwrap(), &[] as &[u8]); + + let view = ByteSliceView::nil(); + assert!(view.read().is_none()); + + // This is what we get when creating a ByteSliceView for an empty []byte in Go + let view = ByteSliceView { + is_nil: false, + ptr: std::ptr::null::(), + len: 0, + }; + assert!(view.read().is_some()); + } + + #[test] + fn byte_slice_view_to_owned_works() { + let data = vec![0xAA, 0xBB, 0xCC]; + let view = ByteSliceView::new(&data); + assert_eq!(view.to_owned().unwrap(), vec![0xAA, 0xBB, 0xCC]); + + let data = vec![]; + let view = ByteSliceView::new(&data); + assert_eq!(view.to_owned().unwrap(), Vec::::new()); + + let view = ByteSliceView::nil(); + assert!(view.to_owned().is_none()); + } + + #[test] + fn unmanaged_vector_new_works() { + // With data + let x = UnmanagedVector::new(Some(vec![0x11, 0x22])); + assert!(!x.is_none); + assert_ne!(x.ptr as usize, 0); + assert_eq!(x.len, 2); + assert_eq!(x.cap, 2); + + // Empty data + let x = UnmanagedVector::new(Some(vec![])); + assert!(!x.is_none); + assert_eq!(x.ptr as usize, 0); + assert_eq!(x.len, 0); + assert_eq!(x.cap, 0); + + // None + let x = UnmanagedVector::new(None); + assert!(x.is_none); + assert_eq!(x.ptr as usize, 0); // this is not guaranteed, could be anything + assert_eq!(x.len, 0); // this is not guaranteed, could be anything + assert_eq!(x.cap, 0); // this is not guaranteed, could be anything + } + + #[test] + fn unmanaged_vector_some_works() { + // With data + let x = UnmanagedVector::some(vec![0x11, 0x22]); + assert!(!x.is_none); + assert_ne!(x.ptr as usize, 0); + assert_eq!(x.len, 2); + assert_eq!(x.cap, 2); + + // Empty data + let x = UnmanagedVector::some(vec![]); + assert!(!x.is_none); + assert_eq!(x.ptr as usize, 0); + assert_eq!(x.len, 0); + assert_eq!(x.cap, 0); + } + + #[test] + fn unmanaged_vector_none_works() { + let x = UnmanagedVector::new(None); + assert!(x.is_none); + + assert_eq!(x.ptr as usize, 0); // this is not guaranteed, could be anything + assert_eq!(x.len, 0); // this is not guaranteed, could be anything + assert_eq!(x.cap, 0); // this is not guaranteed, could be anything + } + + #[test] + fn unmanaged_vector_is_some_works() { + let x = UnmanagedVector::new(Some(vec![0x11, 0x22])); + assert!(x.is_some()); + let x = UnmanagedVector::new(Some(vec![])); + assert!(x.is_some()); + let x = UnmanagedVector::new(None); + assert!(!x.is_some()); + } + + #[test] + fn unmanaged_vector_is_none_works() { + let x = UnmanagedVector::new(Some(vec![0x11, 0x22])); + assert!(!x.is_none()); + let x = UnmanagedVector::new(Some(vec![])); + assert!(!x.is_none()); + let x = UnmanagedVector::new(None); + assert!(x.is_none()); + } + + #[test] + fn unmanaged_vector_consume_works() { + let x = UnmanagedVector::new(Some(vec![0x11, 0x22])); + assert_eq!(x.consume(), Some(vec![0x11u8, 0x22])); + let x = UnmanagedVector::new(Some(vec![])); + assert_eq!(x.consume(), Some(Vec::::new())); + let x = UnmanagedVector::new(None); + assert_eq!(x.consume(), None); + } + + #[test] + fn unmanaged_vector_defaults_to_none() { + let x = UnmanagedVector::default(); + assert_eq!(x.consume(), None); + } + + #[test] + fn new_unmanaged_vector_works() { + // Some simple data + let data = b"some stuff"; + let x = new_unmanaged_vector(false, data.as_ptr(), data.len()); + assert_eq!(x.consume(), Some(Vec::::from(b"some stuff" as &[u8]))); + + // empty created in Rust + let data = b""; + let x = new_unmanaged_vector(false, data.as_ptr(), data.len()); + assert_eq!(x.consume(), Some(Vec::::new())); + + // empty created in Go + let x = new_unmanaged_vector(false, std::ptr::null::(), 0); + assert_eq!(x.consume(), Some(Vec::::new())); + + // nil with garbage pointer + let x = new_unmanaged_vector(true, 345 as *const u8, 46); + assert_eq!(x.consume(), None); + + // nil with empty slice + let data = b""; + let x = new_unmanaged_vector(true, data.as_ptr(), data.len()); + assert_eq!(x.consume(), None); + + // nil with null pointer + let x = new_unmanaged_vector(true, std::ptr::null::(), 0); + assert_eq!(x.consume(), None); + } +} diff --git a/libwasmvm/src/querier.rs b/libwasmvm/src/querier.rs new file mode 100644 index 000000000..993de86c1 --- /dev/null +++ b/libwasmvm/src/querier.rs @@ -0,0 +1,92 @@ +use cosmwasm_std::{Binary, ContractResult, SystemError, SystemResult}; +use cosmwasm_vm::{BackendResult, GasInfo, Querier}; + +use crate::error::GoError; +use crate::memory::{U8SliceView, UnmanagedVector}; +use crate::Vtable; + +// this represents something passed in from the caller side of FFI +#[repr(C)] +#[derive(Clone)] +pub struct querier_t { + _private: [u8; 0], +} + +#[repr(C)] +#[derive(Clone, Default)] +pub struct QuerierVtable { + // We return errors through the return buffer, but may return non-zero error codes on panic + pub query_external: Option< + extern "C" fn( + querier: *const querier_t, + gas_limit: u64, + gas_used: *mut u64, + request: U8SliceView, + result_out: *mut UnmanagedVector, // A JSON encoded SystemResult> + err_msg_out: *mut UnmanagedVector, + ) -> i32, + >, +} + +impl Vtable for QuerierVtable {} + +#[repr(C)] +#[derive(Clone)] +pub struct GoQuerier { + pub state: *const querier_t, + pub vtable: QuerierVtable, +} + +// TODO: check if we can do this safer... +unsafe impl Send for GoQuerier {} + +impl Querier for GoQuerier { + fn query_raw( + &self, + request: &[u8], + gas_limit: u64, + ) -> BackendResult>> { + let mut output = UnmanagedVector::default(); + let mut error_msg = UnmanagedVector::default(); + let mut used_gas = 0_u64; + let query_external = self + .vtable + .query_external + .expect("vtable function 'query_external' not set"); + let go_result: GoError = query_external( + self.state, + gas_limit, + &mut used_gas as *mut u64, + U8SliceView::new(Some(request)), + &mut output as *mut UnmanagedVector, + &mut error_msg as *mut UnmanagedVector, + ) + .into(); + // We destruct the UnmanagedVector here, no matter if we need the data. + let output = output.consume(); + + let gas_info = GasInfo::with_externally_used(used_gas); + + // return complete error message (reading from buffer for GoError::Other) + let default = || { + format!( + "Failed to query another contract with this request: {}", + String::from_utf8_lossy(request) + ) + }; + unsafe { + if let Err(err) = go_result.into_result(error_msg, default) { + return (Err(err), gas_info); + } + } + + let bin_result: Vec = output.unwrap_or_default(); + let sys_result: SystemResult<_> = serde_json::from_slice(&bin_result).unwrap_or_else(|e| { + SystemResult::Err(SystemError::InvalidResponse { + error: format!("Parsing Go response: {e}"), + response: bin_result.into(), + }) + }); + (Ok(sys_result), gas_info) + } +} diff --git a/libwasmvm/src/storage.rs b/libwasmvm/src/storage.rs new file mode 100644 index 000000000..98fcb9375 --- /dev/null +++ b/libwasmvm/src/storage.rs @@ -0,0 +1,211 @@ +use std::collections::HashMap; +use std::convert::TryInto; + +use cosmwasm_std::{Order, Record}; +use cosmwasm_vm::{BackendError, BackendResult, GasInfo, Storage}; + +use crate::db::Db; +use crate::error::GoError; +use crate::iterator::GoIter; +use crate::memory::{U8SliceView, UnmanagedVector}; + +pub struct GoStorage { + db: Db, + iterators: HashMap, +} + +impl GoStorage { + pub fn new(db: Db) -> Self { + GoStorage { + db, + iterators: HashMap::new(), + } + } +} + +impl Storage for GoStorage { + fn get(&self, key: &[u8]) -> BackendResult>> { + let mut output = UnmanagedVector::default(); + let mut error_msg = UnmanagedVector::default(); + let mut used_gas = 0_u64; + let read_db = self + .db + .vtable + .read_db + .expect("vtable function 'read_db' not set"); + let go_error: GoError = read_db( + self.db.state, + self.db.gas_meter, + &mut used_gas as *mut u64, + U8SliceView::new(Some(key)), + &mut output as *mut UnmanagedVector, + &mut error_msg as *mut UnmanagedVector, + ) + .into(); + // We destruct the UnmanagedVector here, no matter if we need the data. + let output = output.consume(); + + let gas_info = GasInfo::with_externally_used(used_gas); + + // return complete error message (reading from buffer for GoError::Other) + let default = || { + format!( + "Failed to read a key in the db: {}", + String::from_utf8_lossy(key) + ) + }; + unsafe { + if let Err(err) = go_error.into_result(error_msg, default) { + return (Err(err), gas_info); + } + } + + (Ok(output), gas_info) + } + + fn scan( + &mut self, + start: Option<&[u8]>, + end: Option<&[u8]>, + order: Order, + ) -> BackendResult { + let mut error_msg = UnmanagedVector::default(); + let mut iter = GoIter::stub(); + let mut used_gas = 0_u64; + let scan_db = self + .db + .vtable + .scan_db + .expect("vtable function 'scan_db' not set"); + let go_error: GoError = scan_db( + self.db.state, + self.db.gas_meter, + &mut used_gas as *mut u64, + U8SliceView::new(start), + U8SliceView::new(end), + order.into(), + &mut iter as *mut GoIter, + &mut error_msg as *mut UnmanagedVector, + ) + .into(); + let gas_info = GasInfo::with_externally_used(used_gas); + + // return complete error message (reading from buffer for GoError::Other) + let default = || { + format!( + "Failed to read the next key between {:?} and {:?}", + start.map(String::from_utf8_lossy), + end.map(String::from_utf8_lossy), + ) + }; + unsafe { + if let Err(err) = go_error.into_result(error_msg, default) { + return (Err(err), gas_info); + } + } + + let next_id: u32 = self + .iterators + .len() + .try_into() + .expect("Iterator count exceeded uint32 range. This is a bug."); + self.iterators.insert(next_id, iter); // This moves iter. Is this okay? + (Ok(next_id), gas_info) + } + + fn next(&mut self, iterator_id: u32) -> BackendResult> { + let Some(iterator) = self.iterators.get_mut(&iterator_id) else { + return ( + Err(BackendError::iterator_does_not_exist(iterator_id)), + GasInfo::free(), + ); + }; + iterator.next() + } + + fn next_key(&mut self, iterator_id: u32) -> BackendResult>> { + let Some(iterator) = self.iterators.get_mut(&iterator_id) else { + return ( + Err(BackendError::iterator_does_not_exist(iterator_id)), + GasInfo::free(), + ); + }; + + iterator.next_key() + } + + fn next_value(&mut self, iterator_id: u32) -> BackendResult>> { + let Some(iterator) = self.iterators.get_mut(&iterator_id) else { + return ( + Err(BackendError::iterator_does_not_exist(iterator_id)), + GasInfo::free(), + ); + }; + + iterator.next_value() + } + + fn set(&mut self, key: &[u8], value: &[u8]) -> BackendResult<()> { + let mut error_msg = UnmanagedVector::default(); + let mut used_gas = 0_u64; + let write_db = self + .db + .vtable + .write_db + .expect("vtable function 'write_db' not set"); + let go_error: GoError = write_db( + self.db.state, + self.db.gas_meter, + &mut used_gas as *mut u64, + U8SliceView::new(Some(key)), + U8SliceView::new(Some(value)), + &mut error_msg as *mut UnmanagedVector, + ) + .into(); + let gas_info = GasInfo::with_externally_used(used_gas); + // return complete error message (reading from buffer for GoError::Other) + let default = || { + format!( + "Failed to set a key in the db: {}", + String::from_utf8_lossy(key), + ) + }; + unsafe { + if let Err(err) = go_error.into_result(error_msg, default) { + return (Err(err), gas_info); + } + } + (Ok(()), gas_info) + } + + fn remove(&mut self, key: &[u8]) -> BackendResult<()> { + let mut error_msg = UnmanagedVector::default(); + let mut used_gas = 0_u64; + let remove_db = self + .db + .vtable + .remove_db + .expect("vtable function 'remove_db' not set"); + let go_error: GoError = remove_db( + self.db.state, + self.db.gas_meter, + &mut used_gas as *mut u64, + U8SliceView::new(Some(key)), + &mut error_msg as *mut UnmanagedVector, + ) + .into(); + let gas_info = GasInfo::with_externally_used(used_gas); + let default = || { + format!( + "Failed to delete a key in the db: {}", + String::from_utf8_lossy(key), + ) + }; + unsafe { + if let Err(err) = go_error.into_result(error_msg, default) { + return (Err(err), gas_info); + } + } + (Ok(()), gas_info) + } +} diff --git a/libwasmvm/src/test_utils.rs b/libwasmvm/src/test_utils.rs new file mode 100644 index 000000000..9dbd08dc8 --- /dev/null +++ b/libwasmvm/src/test_utils.rs @@ -0,0 +1,100 @@ +#![cfg(test)] + +use cosmwasm_std::{Decimal, Uint128}; +use std::str::FromStr as _; + +/// Asserts that two expressions are approximately equal to each other. +/// +/// The `max_rel_diff` argument defines the maximum relative difference +/// of the `left` and `right` values. +/// +/// On panic, this macro will print the values of the arguments and +/// the actual relative difference. +/// +/// Like [`assert_eq!`], this macro has a second form, where a custom +/// panic message can be provided. +#[macro_export] +macro_rules! assert_approx_eq { + ($left:expr, $right:expr, $max_rel_diff:expr $(,)?) => {{ + $crate::test_utils::assert_approx_eq_impl($left, $right, $max_rel_diff, None); + }}; + ($left:expr, $right:expr, $max_rel_diff:expr, $($args:tt)+) => {{ + $crate::test_utils::assert_approx_eq_impl($left, $right, $max_rel_diff, Some(format!($($args)*))); + }}; +} + +#[track_caller] +pub fn assert_approx_eq_impl>( + left: U, + right: U, + max_rel_diff: &str, + panic_msg: Option, +) { + let left = left.into(); + let right = right.into(); + let max_rel_diff = Decimal::from_str(max_rel_diff).unwrap(); + + let largest = std::cmp::max(left, right); + let rel_diff = Decimal::from_ratio(left.abs_diff(right), largest); + + if rel_diff > max_rel_diff { + match panic_msg { + Some(panic_msg) => panic!( + "assertion failed: `(left ≈ right)`\nleft: {left}\nright: {right}\nrelative difference: {rel_diff}\nmax allowed relative difference: {max_rel_diff}\n: {panic_msg}" + ), + None => panic!( + "assertion failed: `(left ≈ right)`\nleft: {left}\nright: {right}\nrelative difference: {rel_diff}\nmax allowed relative difference: {max_rel_diff}\n" + ), + } + } +} + +mod tests { + #[test] + fn assert_approx() { + assert_approx_eq!(9_u32, 10_u32, "0.12"); + assert_approx_eq!(9_u64, 10_u64, "0.12"); + assert_approx_eq!( + 9_000_000_000_000_000_000_000_000_000_000_000_000_u128, + 10_000_000_000_000_000_000_000_000_000_000_000_000_u128, + "0.10" + ); + } + + #[test] + fn assert_approx_with_vars() { + let a = 66_u32; + let b = 67_u32; + assert_approx_eq!(a, b, "0.02"); + + let a = 66_u64; + let b = 67_u64; + assert_approx_eq!(a, b, "0.02"); + + let a = 66_u128; + let b = 67_u128; + assert_approx_eq!(a, b, "0.02"); + } + + #[test] + #[should_panic( + expected = "assertion failed: `(left ≈ right)`\nleft: 8\nright: 10\nrelative difference: 0.2\nmax allowed relative difference: 0.12\n" + )] + fn assert_approx_fail() { + assert_approx_eq!(8_u32, 10_u32, "0.12"); + } + + #[test] + #[should_panic( + expected = "assertion failed: `(left ≈ right)`\nleft: 17\nright: 20\nrelative difference: 0.15\nmax allowed relative difference: 0.12\n: some extra info about the error" + )] + fn assert_approx_with_custom_panic_msg() { + assert_approx_eq!( + 17_u32, + 20_u32, + "0.12", + "some extra {} about the error", + "info" + ); + } +} diff --git a/libwasmvm/src/tests.rs b/libwasmvm/src/tests.rs new file mode 100644 index 000000000..5c8a7f179 --- /dev/null +++ b/libwasmvm/src/tests.rs @@ -0,0 +1,83 @@ +#![cfg(test)] + +use tempfile::TempDir; + +use cosmwasm_vm::testing::{mock_backend, mock_env, mock_info, mock_instance_with_gas_limit}; +use cosmwasm_vm::{ + call_execute_raw, call_instantiate_raw, capabilities_from_csv, to_vec, Cache, CacheOptions, + InstanceOptions, Size, +}; + +static CYBERPUNK: &[u8] = include_bytes!("../../testdata/cyberpunk.wasm"); +const MEMORY_CACHE_SIZE: Size = Size::mebi(200); +const MEMORY_LIMIT: Size = Size::mebi(32); +const GAS_LIMIT: u64 = 200_000_000_000; // ~0.2ms + +#[test] +fn handle_cpu_loop_with_cache() { + let backend = mock_backend(&[]); + let options = CacheOptions::new( + TempDir::new().unwrap().path().to_path_buf(), + capabilities_from_csv("staking"), + MEMORY_CACHE_SIZE, + MEMORY_LIMIT, + ); + let cache = unsafe { Cache::new(options) }.unwrap(); + + let options = InstanceOptions { + gas_limit: GAS_LIMIT, + }; + + // store code + let checksum = cache.store_code(CYBERPUNK, true, true).unwrap(); + + // instantiate + let env = mock_env(); + let info = mock_info("creator", &[]); + let mut instance = cache.get_instance(&checksum, backend, options).unwrap(); + let raw_env = to_vec(&env).unwrap(); + let raw_info = to_vec(&info).unwrap(); + let res = call_instantiate_raw(&mut instance, &raw_env, &raw_info, b"{}"); + let gas_left = instance.get_gas_left(); + let gas_used = options.gas_limit - gas_left; + println!("Init gas left: {gas_left}, used: {gas_used}"); + assert!(res.is_ok()); + let backend = instance.recycle().unwrap(); + + // execute + let mut instance = cache.get_instance(&checksum, backend, options).unwrap(); + let raw_msg = br#"{"cpu_loop":{}}"#; + let res = call_execute_raw(&mut instance, &raw_env, &raw_info, raw_msg); + let gas_left = instance.get_gas_left(); + let gas_used = options.gas_limit - gas_left; + println!("Handle gas left: {gas_left}, used: {gas_used}"); + assert!(res.is_err()); + assert_eq!(gas_left, 0); + let _ = instance.recycle(); +} + +#[test] +fn handle_cpu_loop_no_cache() { + let gas_limit = GAS_LIMIT; + let mut instance = mock_instance_with_gas_limit(CYBERPUNK, gas_limit); + + // instantiate + let env = mock_env(); + let info = mock_info("creator", &[]); + let raw_env = to_vec(&env).unwrap(); + let raw_info = to_vec(&info).unwrap(); + let res = call_instantiate_raw(&mut instance, &raw_env, &raw_info, b"{}"); + let gas_left = instance.get_gas_left(); + let gas_used = gas_limit - gas_left; + println!("Init gas left: {gas_left}, used: {gas_used}"); + assert!(res.is_ok()); + + // execute + let raw_msg = br#"{"cpu_loop":{}}"#; + let res = call_execute_raw(&mut instance, &raw_env, &raw_info, raw_msg); + let gas_left = instance.get_gas_left(); + let gas_used = gas_limit - gas_left; + println!("Handle gas left: {gas_left}, used: {gas_used}"); + assert!(res.is_err()); + assert_eq!(gas_left, 0); +} diff --git a/libwasmvm/src/version.rs b/libwasmvm/src/version.rs new file mode 100644 index 000000000..a0181d8b0 --- /dev/null +++ b/libwasmvm/src/version.rs @@ -0,0 +1,56 @@ +use std::os::raw::c_char; + +static VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0"); // Add trailing NULL byte for C string + +/// Returns a version number of this library as a C string. +/// +/// The string is owned by libwasmvm and must not be mutated or destroyed by the caller. +#[no_mangle] +pub extern "C" fn version_str() -> *const c_char { + VERSION.as_ptr() as *const _ +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::CStr; + + #[test] + fn version_works() { + // Returns the same pointer every time + let ptr1 = version_str(); + let ptr2 = version_str(); + assert_eq!(ptr1, ptr2); + + // Contains correct data + let version_ptr = version_str(); + let version_str = unsafe { CStr::from_ptr(version_ptr) }.to_str().unwrap(); + // assert_eq!(version_str, "1.2.3"); + + let mut parts = version_str.split('-'); + let version_core = parts.next().unwrap(); + let components = version_core.split('.').collect::>(); + assert_eq!(components.len(), 3); + assert!( + components[0].chars().all(|c| c.is_ascii_digit()), + "Invalid major component: '{}'", + components[0] + ); + assert!( + components[1].chars().all(|c| c.is_ascii_digit()), + "Invalid minor component: '{}'", + components[1] + ); + assert!( + components[2].chars().all(|c| c.is_ascii_digit()), + "Invalid patch component: '{}'", + components[2] + ); + if let Some(prerelease) = parts.next() { + assert!(prerelease + .chars() + .all(|c| c == '.' || c.is_ascii_alphanumeric())); + } + assert_eq!(parts.next(), None); + } +} diff --git a/libwasmvm/src/vtables.rs b/libwasmvm/src/vtables.rs new file mode 100644 index 000000000..eca736ff3 --- /dev/null +++ b/libwasmvm/src/vtables.rs @@ -0,0 +1,46 @@ +/// Vtables are a collection of free function. Those functions are created in Go and +/// then assigned via function pointers that Rust can call. The functions do not +/// change during the lifetime of the object. +/// +/// Since the functions are free, a single function is used for multiple instances. +/// In fact, in Go we create a single global vtable variable holding and owning the +/// function the vtables in Rust point to. +/// +/// The `Vtable` trait is created to find those vtables throughout the codebase. +/// +/// ## Nullability +/// +/// Since all functions are represented as a function pointer, they are naturally +/// nullable. I.e. in Go we can always set them to `nil`. For this reason the functions +/// are all wrapped in Options, e.g. +/// +/// ``` +/// # use wasmvm::UnmanagedVector; +/// # struct IteratorReference; +/// # struct gas_meter_t; +/// pub struct IteratorVtable { +/// pub next: Option< +/// extern "C" fn( +/// iterator: IteratorReference, +/// gas_meter: *mut gas_meter_t, +/// gas_used: *mut u64, +/// key_out: *mut UnmanagedVector, +/// value_out: *mut UnmanagedVector, +/// err_msg_out: *mut UnmanagedVector, +/// ) -> i32, +/// >, +/// // ... +/// } +/// ``` +/// +/// Or to say it in the words of [the Rust documentation](https://doc.rust-lang.org/nomicon/ffi.html#the-nullable-pointer-optimization): +/// +/// > So `Option c_int>` is a correct way to represent a nullable function pointer using the C ABI (corresponding to the C type `int (*)(int))`. +/// +/// Since all vtable fields are nullable, we can easily demand them +/// to have a Default implementation. This sometimes is handy when a +/// type is created in Rust and then filled in Go. +/// +/// Missing vtable fields always indicate a lifecycle bug in wasmvm and +/// should be treated with a panic. +pub trait Vtable: Default {}