diff --git a/go.mod b/go.mod index e5f461b..b6a4043 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/dvsekhvalnov/jose2go v1.5.0 github.com/gogo/protobuf v1.3.3 github.com/golang/protobuf v1.5.3 + github.com/google/btree v1.1.2 github.com/google/gofuzz v1.2.0 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 @@ -77,7 +78,6 @@ require ( github.com/golang/glog v1.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect - github.com/google/btree v1.1.2 // indirect github.com/google/flatbuffers v1.12.1 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/google/uuid v1.3.0 // indirect diff --git a/x/wasm/artifacts/v152/api/api_test.go b/x/wasm/artifacts/v152/api/api_test.go new file mode 100644 index 0000000..fbc5713 --- /dev/null +++ b/x/wasm/artifacts/v152/api/api_test.go @@ -0,0 +1,47 @@ +package api + +import ( + "encoding/json" + "io/ioutil" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/CosmWasm/wasmvm/types" +) + +func TestValidateAddressFailure(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + + // create contract + wasm, err := ioutil.ReadFile("../../testdata/hackatom.wasm") + require.NoError(t, err) + checksum, err := StoreCode(cache, wasm) + require.NoError(t, err) + + gasMeter := NewMockGasMeter(TESTING_GAS_LIMIT) + // instantiate it with this store + store := NewLookup(gasMeter) + api := NewMockAPI() + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, types.Coins{types.NewCoin(100, "ATOM")}) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + + // if the human address is larger than 32 bytes, this will lead to an error in the go side + longName := "long123456789012345678901234567890long" + msg := []byte(`{"verifier": "` + longName + `", "beneficiary": "bob"}`) + + // make sure the call doesn't error, but we get a JSON-encoded error result from ContractResult + igasMeter := types.GasMeter(gasMeter) + res, _, err := Instantiate(cache, checksum, env, info, msg, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + var result types.ContractResult + err = json.Unmarshal(res, &result) + require.NoError(t, err) + + // ensure the error message is what we expect + require.Nil(t, result.Ok) + // with this error + require.Equal(t, "Generic error: addr_validate errored: human encoding too long", result.Err) +} diff --git a/x/wasm/artifacts/v152/api/bindings.h b/x/wasm/artifacts/v152/api/bindings.h new file mode 100644 index 0000000..6bb960b --- /dev/null +++ b/x/wasm/artifacts/v152/api/bindings.h @@ -0,0 +1,569 @@ +/* (c) 2019 Confio OÜ. Licensed under Apache-2.0 */ + +/* Generated with cbindgen:0.24.5 */ + +/* 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 passted 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 go_error: GoError = (db.vtable.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 comsume 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 underylying 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; + +/** + * 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 { + bool has_ibc_entry_points; + /** + * An UTF-8 encoded comma separated list of reqired capabilities. + * This is never None/nil. + */ + struct UnmanagedVector required_capabilities; +} 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; + +typedef struct iterator_t { + /** + * An ID assigned to this contract call + */ + uint64_t call_id; + uint64_t iterator_index; +} iterator_t; + +typedef struct Iterator_vtable { + int32_t (*next)(struct iterator_t, + struct gas_meter_t*, + uint64_t*, + struct UnmanagedVector*, + struct UnmanagedVector*, + struct UnmanagedVector*); + int32_t (*next_key)(struct iterator_t, + struct gas_meter_t*, + uint64_t*, + struct UnmanagedVector*, + struct UnmanagedVector*); + int32_t (*next_value)(struct iterator_t, + struct gas_meter_t*, + uint64_t*, + struct UnmanagedVector*, + struct UnmanagedVector*); +} Iterator_vtable; + +typedef struct GoIter { + struct gas_meter_t *gas_meter; + struct iterator_t state; + struct Iterator_vtable vtable; +} GoIter; + +typedef struct Db_vtable { + int32_t (*read_db)(struct db_t*, + struct gas_meter_t*, + uint64_t*, + struct U8SliceView, + struct UnmanagedVector*, + struct UnmanagedVector*); + int32_t (*write_db)(struct db_t*, + struct gas_meter_t*, + uint64_t*, + struct U8SliceView, + struct U8SliceView, + struct UnmanagedVector*); + int32_t (*remove_db)(struct db_t*, + struct gas_meter_t*, + uint64_t*, + struct U8SliceView, + struct UnmanagedVector*); + int32_t (*scan_db)(struct db_t*, + struct gas_meter_t*, + uint64_t*, + struct U8SliceView, + struct U8SliceView, + int32_t, + struct GoIter*, + struct UnmanagedVector*); +} Db_vtable; + +typedef struct Db { + struct gas_meter_t *gas_meter; + struct db_t *state; + struct Db_vtable vtable; +} Db; + +typedef struct api_t { + uint8_t _private[0]; +} api_t; + +typedef struct GoApi_vtable { + int32_t (*humanize_address)(const struct api_t*, + struct U8SliceView, + struct UnmanagedVector*, + struct UnmanagedVector*, + uint64_t*); + int32_t (*canonicalize_address)(const struct api_t*, + struct U8SliceView, + struct UnmanagedVector*, + struct UnmanagedVector*, + uint64_t*); +} GoApi_vtable; + +typedef struct GoApi { + const struct api_t *state; + struct GoApi_vtable vtable; +} GoApi; + +typedef struct querier_t { + uint8_t _private[0]; +} querier_t; + +typedef struct Querier_vtable { + int32_t (*query_external)(const struct querier_t*, + uint64_t, + uint64_t*, + struct U8SliceView, + struct UnmanagedVector*, + struct UnmanagedVector*); +} Querier_vtable; + +typedef struct GoQuerier { + const struct querier_t *state; + struct Querier_vtable 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 data_dir, + struct ByteSliceView available_capabilities, + uint32_t cache_size, + uint32_t instance_memory_limit, + struct UnmanagedVector *error_msg); + +struct UnmanagedVector save_wasm(struct cache_t *cache, + struct ByteSliceView wasm, + bool unchecked, + 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); + +/** + * 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 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 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/x/wasm/artifacts/v152/api/callbacks.go b/x/wasm/artifacts/v152/api/callbacks.go new file mode 100644 index 0000000..fd06b8c --- /dev/null +++ b/x/wasm/artifacts/v152/api/callbacks.go @@ -0,0 +1,481 @@ +package api + +// Check https://akrennmair.github.io/golang-cgo-slides/ to learn +// how this embedded C code works. + +/* +#include "bindings.h" + +// typedefs for _cgo functions (db) +typedef GoError (*read_db_fn)(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView key, UnmanagedVector *val, UnmanagedVector *errOut); +typedef GoError (*write_db_fn)(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView key, U8SliceView val, UnmanagedVector *errOut); +typedef GoError (*remove_db_fn)(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView key, UnmanagedVector *errOut); +typedef GoError (*scan_db_fn)(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView start, U8SliceView end, int32_t order, GoIter *out, UnmanagedVector *errOut); +// iterator +typedef GoError (*db_next)(iterator_t idx, gas_meter_t *gas_meter, uint64_t *used_gas, UnmanagedVector *key, UnmanagedVector *val, UnmanagedVector *errOut); +typedef GoError (*db_next_key)(iterator_t idx, gas_meter_t *gas_meter, uint64_t *used_gas, UnmanagedVector *key, UnmanagedVector *errOut); +typedef GoError (*db_next_value)(iterator_t idx, gas_meter_t *gas_meter, uint64_t *used_gas, UnmanagedVector *val, UnmanagedVector *errOut); +// and api +typedef GoError (*humanize_address_fn)(api_t *ptr, U8SliceView src, UnmanagedVector *dest, UnmanagedVector *errOut, uint64_t *used_gas); +typedef GoError (*canonicalize_address_fn)(api_t *ptr, U8SliceView src, UnmanagedVector *dest, UnmanagedVector *errOut, uint64_t *used_gas); +typedef GoError (*query_external_fn)(querier_t *ptr, uint64_t gas_limit, uint64_t *used_gas, U8SliceView request, UnmanagedVector *result, UnmanagedVector *errOut); + +// forward declarations (db) +GoError cGet_cgo(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView key, UnmanagedVector *val, UnmanagedVector *errOut); +GoError cSet_cgo(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView key, U8SliceView val, UnmanagedVector *errOut); +GoError cDelete_cgo(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView key, UnmanagedVector *errOut); +GoError cScan_cgo(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView start, U8SliceView end, int32_t order, GoIter *out, UnmanagedVector *errOut); +// iterator +GoError cNext_cgo(iterator_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, UnmanagedVector *key, UnmanagedVector *val, UnmanagedVector *errOut); +GoError cNextKey_cgo(iterator_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, UnmanagedVector *key, UnmanagedVector *errOut); +GoError cNextValue_cgo(iterator_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, UnmanagedVector *val, UnmanagedVector *errOut); +// api +GoError cHumanAddress_cgo(api_t *ptr, U8SliceView src, UnmanagedVector *dest, UnmanagedVector *errOut, uint64_t *used_gas); +GoError cCanonicalAddress_cgo(api_t *ptr, U8SliceView src, UnmanagedVector *dest, UnmanagedVector *errOut, uint64_t *used_gas); +// and querier +GoError cQueryExternal_cgo(querier_t *ptr, uint64_t gas_limit, uint64_t *used_gas, U8SliceView request, UnmanagedVector *result, UnmanagedVector *errOut); + + +*/ +import "C" + +import ( + "encoding/json" + "fmt" + "log" + "reflect" + "runtime/debug" + "unsafe" + + "github.com/CosmWasm/wasmvm/types" +) + +// Note: we have to include all exports in the same file (at least since they both import bindings.h), +// or get odd cgo build errors about duplicate definitions + +func recoverPanic(ret *C.GoError) { + if rec := recover(); rec != nil { + // This is used to handle ErrorOutOfGas panics. + // + // What we do here is something that should not be done in the first place. + // "A panic typically means something went unexpectedly wrong. Mostly we use it to fail fast + // on errors that shouldn’t occur during normal operation, or that we aren’t prepared to + // handle gracefully." says https://gobyexample.com/panic. + // And 'Ask yourself "when this happens, should the application immediately crash?" If yes, + // use a panic; otherwise, use an error.' says this popular answer on SO: https://stackoverflow.com/a/44505268. + // Oh, and "If you're already worrying about discriminating different kinds of panics, you've lost sight of the ball." + // (Rob Pike) from https://eli.thegreenplace.net/2018/on-the-uses-and-misuses-of-panics-in-go/ + // + // We don't want to import Cosmos SDK and also cannot use interfaces to detect these + // error types (as they have no methods). So, let's just rely on the descriptive names. + name := reflect.TypeOf(rec).Name() + switch name { + // These three types are "thrown" (which is not a thing in Go 🙃) in panics from the gas module + // (https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/store/types/gas.go): + // 1. ErrorOutOfGas + // 2. ErrorGasOverflow + // 3. ErrorNegativeGasConsumed + // + // In the baseapp, ErrorOutOfGas gets special treatment: + // - https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/baseapp/baseapp.go#L607 + // - https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/baseapp/recovery.go#L50-L60 + // This turns the panic into a regular error with a helpful error message. + // + // The other two gas related panic types indicate programming errors and are handled along + // with all other errors in https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/baseapp/recovery.go#L66-L77. + case "ErrorOutOfGas": + // TODO: figure out how to pass the text in its `Descriptor` field through all the FFI + *ret = C.GoError_OutOfGas + default: + log.Printf("Panic in Go callback: %#v\n", rec) + debug.PrintStack() + *ret = C.GoError_Panic + } + } +} + +/****** DB ********/ + +var db_vtable = C.Db_vtable{ + read_db: (C.read_db_fn)(C.cGet_cgo), + write_db: (C.write_db_fn)(C.cSet_cgo), + remove_db: (C.remove_db_fn)(C.cDelete_cgo), + scan_db: (C.scan_db_fn)(C.cScan_cgo), +} + +type DBState struct { + Store types.KVStore + // CallID is used to lookup the proper frame for iterators associated with this contract call (iterator.go) + CallID uint64 +} + +// use this to create C.Db in two steps, so the pointer lives as long as the calling stack +// +// state := buildDBState(kv, callID) +// db := buildDB(&state, &gasMeter) +// // then pass db into some FFI function +func buildDBState(kv types.KVStore, callID uint64) DBState { + return DBState{ + Store: kv, + CallID: callID, + } +} + +// contract: original pointer/struct referenced must live longer than C.Db struct +// since this is only used internally, we can verify the code that this is the case +func buildDB(state *DBState, gm *types.GasMeter) C.Db { + return C.Db{ + gas_meter: (*C.gas_meter_t)(unsafe.Pointer(gm)), + state: (*C.db_t)(unsafe.Pointer(state)), + vtable: db_vtable, + } +} + +var iterator_vtable = C.Iterator_vtable{ + next: (C.db_next)(C.cNext_cgo), + next_key: (C.db_next_key)(C.cNextKey_cgo), + next_value: (C.db_next_value)(C.cNextValue_cgo), +} + +// An iterator including referenced objects is 117 bytes large (calculated using https://github.com/DmitriyVTitov/size). +// We limit the number of iterators per contract call ID here in order limit memory usage to 32768*117 = ~3.8 MB as a safety measure. +// In any reasonable contract, gas limits should hit sooner than that though. +const frameLenLimit = 32768 + +// contract: original pointer/struct referenced must live longer than C.Db struct +// since this is only used internally, we can verify the code that this is the case +func buildIterator(callID uint64, it types.Iterator) (C.iterator_t, error) { + idx, err := storeIterator(callID, it, frameLenLimit) + if err != nil { + return C.iterator_t{}, err + } + return C.iterator_t{ + call_id: cu64(callID), + iterator_index: cu64(idx), + }, nil +} + +//export cGet +func cGet(ptr *C.db_t, gasMeter *C.gas_meter_t, usedGas *cu64, key C.U8SliceView, val *C.UnmanagedVector, errOut *C.UnmanagedVector) (ret C.GoError) { + defer recoverPanic(&ret) + + if ptr == nil || gasMeter == nil || usedGas == nil || val == nil || errOut == nil { + // we received an invalid pointer + return C.GoError_BadArgument + } + if !(*val).is_none || !(*errOut).is_none { + panic("Got a non-none UnmanagedVector we're about to override. This is a bug because someone has to drop the old one.") + } + + gm := *(*types.GasMeter)(unsafe.Pointer(gasMeter)) + kv := *(*types.KVStore)(unsafe.Pointer(ptr)) + k := copyU8Slice(key) + + gasBefore := gm.GasConsumed() + v := kv.Get(k) + gasAfter := gm.GasConsumed() + *usedGas = (cu64)(gasAfter - gasBefore) + + // v will equal nil when the key is missing + // https://github.com/cosmos/cosmos-sdk/blob/1083fa948e347135861f88e07ec76b0314296832/store/types/store.go#L174 + *val = newUnmanagedVector(v) + + return C.GoError_None +} + +//export cSet +func cSet(ptr *C.db_t, gasMeter *C.gas_meter_t, usedGas *cu64, key C.U8SliceView, val C.U8SliceView, errOut *C.UnmanagedVector) (ret C.GoError) { + defer recoverPanic(&ret) + + if ptr == nil || gasMeter == nil || usedGas == nil || errOut == nil { + // we received an invalid pointer + return C.GoError_BadArgument + } + if !(*errOut).is_none { + panic("Got a non-none UnmanagedVector we're about to override. This is a bug because someone has to drop the old one.") + } + + gm := *(*types.GasMeter)(unsafe.Pointer(gasMeter)) + kv := *(*types.KVStore)(unsafe.Pointer(ptr)) + k := copyU8Slice(key) + v := copyU8Slice(val) + + gasBefore := gm.GasConsumed() + kv.Set(k, v) + gasAfter := gm.GasConsumed() + *usedGas = (cu64)(gasAfter - gasBefore) + + return C.GoError_None +} + +//export cDelete +func cDelete(ptr *C.db_t, gasMeter *C.gas_meter_t, usedGas *cu64, key C.U8SliceView, errOut *C.UnmanagedVector) (ret C.GoError) { + defer recoverPanic(&ret) + + if ptr == nil || gasMeter == nil || usedGas == nil || errOut == nil { + // we received an invalid pointer + return C.GoError_BadArgument + } + if !(*errOut).is_none { + panic("Got a non-none UnmanagedVector we're about to override. This is a bug because someone has to drop the old one.") + } + + gm := *(*types.GasMeter)(unsafe.Pointer(gasMeter)) + kv := *(*types.KVStore)(unsafe.Pointer(ptr)) + k := copyU8Slice(key) + + gasBefore := gm.GasConsumed() + kv.Delete(k) + gasAfter := gm.GasConsumed() + *usedGas = (cu64)(gasAfter - gasBefore) + + return C.GoError_None +} + +//export cScan +func cScan(ptr *C.db_t, gasMeter *C.gas_meter_t, usedGas *cu64, start C.U8SliceView, end C.U8SliceView, order ci32, out *C.GoIter, errOut *C.UnmanagedVector) (ret C.GoError) { + defer recoverPanic(&ret) + + if ptr == nil || gasMeter == nil || usedGas == nil || out == nil || errOut == nil { + // we received an invalid pointer + return C.GoError_BadArgument + } + if !(*errOut).is_none { + panic("Got a non-none UnmanagedVector we're about to override. This is a bug because someone has to drop the old one.") + } + + gm := *(*types.GasMeter)(unsafe.Pointer(gasMeter)) + state := (*DBState)(unsafe.Pointer(ptr)) + kv := state.Store + s := copyU8Slice(start) + e := copyU8Slice(end) + + var iter types.Iterator + gasBefore := gm.GasConsumed() + switch order { + case 1: // Ascending + iter = kv.Iterator(s, e) + case 2: // Descending + iter = kv.ReverseIterator(s, e) + default: + return C.GoError_BadArgument + } + gasAfter := gm.GasConsumed() + *usedGas = (cu64)(gasAfter - gasBefore) + + cIterator, err := buildIterator(state.CallID, iter) + if err != nil { + // store the actual error message in the return buffer + *errOut = newUnmanagedVector([]byte(err.Error())) + return C.GoError_User + } + + out.state = cIterator + out.vtable = iterator_vtable + return C.GoError_None +} + +//export cNext +func cNext(ref C.iterator_t, gasMeter *C.gas_meter_t, usedGas *cu64, key *C.UnmanagedVector, val *C.UnmanagedVector, errOut *C.UnmanagedVector) (ret C.GoError) { + // typical usage of iterator + // for ; itr.Valid(); itr.Next() { + // k, v := itr.Key(); itr.Value() + // ... + // } + + defer recoverPanic(&ret) + if ref.call_id == 0 || gasMeter == nil || usedGas == nil || key == nil || val == nil || errOut == nil { + // we received an invalid pointer + return C.GoError_BadArgument + } + if !(*key).is_none || !(*val).is_none || !(*errOut).is_none { + panic("Got a non-none UnmanagedVector we're about to override. This is a bug because someone has to drop the old one.") + } + + gm := *(*types.GasMeter)(unsafe.Pointer(gasMeter)) + iter := retrieveIterator(uint64(ref.call_id), uint64(ref.iterator_index)) + if iter == nil { + panic("Unable to retrieve iterator.") + } + if !iter.Valid() { + // end of iterator, return as no-op, nil key is considered end + return C.GoError_None + } + + gasBefore := gm.GasConsumed() + // call Next at the end, upon creation we have first data loaded + k := iter.Key() + v := iter.Value() + // check iter.Error() ???? + iter.Next() + gasAfter := gm.GasConsumed() + *usedGas = (cu64)(gasAfter - gasBefore) + + *key = newUnmanagedVector(k) + *val = newUnmanagedVector(v) + return C.GoError_None +} + +//export cNextKey +func cNextKey(ref C.iterator_t, gasMeter *C.gas_meter_t, usedGas *cu64, key *C.UnmanagedVector, errOut *C.UnmanagedVector) (ret C.GoError) { + return nextPart(ref, gasMeter, usedGas, key, errOut, func(iter types.Iterator) []byte { return iter.Key() }) +} + +//export cNextValue +func cNextValue(ref C.iterator_t, gasMeter *C.gas_meter_t, usedGas *cu64, value *C.UnmanagedVector, errOut *C.UnmanagedVector) (ret C.GoError) { + return nextPart(ref, gasMeter, usedGas, value, errOut, func(iter types.Iterator) []byte { return iter.Value() }) +} + +// nextPart is a helper function that contains the shared code for key- and value-only iteration. +func nextPart(ref C.iterator_t, gasMeter *C.gas_meter_t, usedGas *cu64, output *C.UnmanagedVector, errOut *C.UnmanagedVector, valFn func(types.Iterator) []byte) (ret C.GoError) { + // typical usage of iterator + // for ; itr.Valid(); itr.Next() { + // k, v := itr.Key(); itr.Value() + // ... + // } + + defer recoverPanic(&ret) + if ref.call_id == 0 || gasMeter == nil || usedGas == nil || output == nil || errOut == nil { + // we received an invalid pointer + return C.GoError_BadArgument + } + if !(*output).is_none || !(*errOut).is_none { + panic("Got a non-none UnmanagedVector we're about to override. This is a bug because someone has to drop the old one.") + } + + gm := *(*types.GasMeter)(unsafe.Pointer(gasMeter)) + iter := retrieveIterator(uint64(ref.call_id), uint64(ref.iterator_index)) + if iter == nil { + panic("Unable to retrieve iterator.") + } + if !iter.Valid() { + // end of iterator, return as no-op, nil `output` is considered end + return C.GoError_None + } + + gasBefore := gm.GasConsumed() + // call Next at the end, upon creation we have first data loaded + out := valFn(iter) + // check iter.Error() ???? + iter.Next() + gasAfter := gm.GasConsumed() + *usedGas = (cu64)(gasAfter - gasBefore) + + *output = newUnmanagedVector(out) + return C.GoError_None +} + +var api_vtable = C.GoApi_vtable{ + humanize_address: (C.humanize_address_fn)(C.cHumanAddress_cgo), + canonicalize_address: (C.canonicalize_address_fn)(C.cCanonicalAddress_cgo), +} + +// contract: original pointer/struct referenced must live longer than C.GoApi struct +// since this is only used internally, we can verify the code that this is the case +func buildAPI(api *types.GoAPI) C.GoApi { + return C.GoApi{ + state: (*C.api_t)(unsafe.Pointer(api)), + vtable: api_vtable, + } +} + +//export cHumanAddress +func cHumanAddress(ptr *C.api_t, src C.U8SliceView, dest *C.UnmanagedVector, errOut *C.UnmanagedVector, used_gas *cu64) (ret C.GoError) { + defer recoverPanic(&ret) + + if dest == nil || errOut == nil { + return C.GoError_BadArgument + } + if !(*dest).is_none || !(*errOut).is_none { + panic("Got a non-none UnmanagedVector we're about to override. This is a bug because someone has to drop the old one.") + } + + api := (*types.GoAPI)(unsafe.Pointer(ptr)) + s := copyU8Slice(src) + + h, cost, err := api.HumanAddress(s) + *used_gas = cu64(cost) + if err != nil { + // store the actual error message in the return buffer + *errOut = newUnmanagedVector([]byte(err.Error())) + return C.GoError_User + } + if len(h) == 0 { + panic(fmt.Sprintf("`api.HumanAddress()` returned an empty string for %q", s)) + } + *dest = newUnmanagedVector([]byte(h)) + return C.GoError_None +} + +//export cCanonicalAddress +func cCanonicalAddress(ptr *C.api_t, src C.U8SliceView, dest *C.UnmanagedVector, errOut *C.UnmanagedVector, used_gas *cu64) (ret C.GoError) { + defer recoverPanic(&ret) + + if dest == nil || errOut == nil { + return C.GoError_BadArgument + } + if !(*dest).is_none || !(*errOut).is_none { + panic("Got a non-none UnmanagedVector we're about to override. This is a bug because someone has to drop the old one.") + } + + api := (*types.GoAPI)(unsafe.Pointer(ptr)) + s := string(copyU8Slice(src)) + c, cost, err := api.CanonicalAddress(s) + *used_gas = cu64(cost) + if err != nil { + // store the actual error message in the return buffer + *errOut = newUnmanagedVector([]byte(err.Error())) + return C.GoError_User + } + if len(c) == 0 { + panic(fmt.Sprintf("`api.CanonicalAddress()` returned an empty string for %q", s)) + } + *dest = newUnmanagedVector(c) + return C.GoError_None +} + +/****** Go Querier ********/ + +var querier_vtable = C.Querier_vtable{ + query_external: (C.query_external_fn)(C.cQueryExternal_cgo), +} + +// contract: original pointer/struct referenced must live longer than C.GoQuerier struct +// since this is only used internally, we can verify the code that this is the case +func buildQuerier(q *Querier) C.GoQuerier { + return C.GoQuerier{ + state: (*C.querier_t)(unsafe.Pointer(q)), + vtable: querier_vtable, + } +} + +//export cQueryExternal +func cQueryExternal(ptr *C.querier_t, gasLimit cu64, usedGas *cu64, request C.U8SliceView, result *C.UnmanagedVector, errOut *C.UnmanagedVector) (ret C.GoError) { + defer recoverPanic(&ret) + + if ptr == nil || usedGas == nil || result == nil || errOut == nil { + // we received an invalid pointer + return C.GoError_BadArgument + } + if !(*result).is_none || !(*errOut).is_none { + panic("Got a non-none UnmanagedVector we're about to override. This is a bug because someone has to drop the old one.") + } + + // query the data + querier := *(*Querier)(unsafe.Pointer(ptr)) + req := copyU8Slice(request) + + gasBefore := querier.GasConsumed() + res := types.RustQuery(querier, req, uint64(gasLimit)) + gasAfter := querier.GasConsumed() + *usedGas = (cu64)(gasAfter - gasBefore) + + // serialize the response + bz, err := json.Marshal(res) + if err != nil { + *errOut = newUnmanagedVector([]byte(err.Error())) + return C.GoError_CannotSerialize + } + *result = newUnmanagedVector(bz) + return C.GoError_None +} diff --git a/x/wasm/artifacts/v152/api/callbacks_cgo.go b/x/wasm/artifacts/v152/api/callbacks_cgo.go new file mode 100644 index 0000000..343c890 --- /dev/null +++ b/x/wasm/artifacts/v152/api/callbacks_cgo.go @@ -0,0 +1,65 @@ +package api + +/* +#include "bindings.h" +#include + +// imports (db) +GoError cSet(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView key, U8SliceView val, UnmanagedVector *errOut); +GoError cGet(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView key, UnmanagedVector *val, UnmanagedVector *errOut); +GoError cDelete(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView key, UnmanagedVector *errOut); +GoError cScan(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView start, U8SliceView end, int32_t order, GoIter *out, UnmanagedVector *errOut); +// imports (iterator) +GoError cNext(iterator_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, UnmanagedVector *key, UnmanagedVector *val, UnmanagedVector *errOut); +GoError cNextKey(iterator_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, UnmanagedVector *key, UnmanagedVector *errOut); +GoError cNextValue(iterator_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, UnmanagedVector *value, UnmanagedVector *errOut); +// imports (api) +GoError cHumanAddress(api_t *ptr, U8SliceView src, UnmanagedVector *dest, UnmanagedVector *errOut, uint64_t *used_gas); +GoError cCanonicalAddress(api_t *ptr, U8SliceView src, UnmanagedVector *dest, UnmanagedVector *errOut, uint64_t *used_gas); +// imports (querier) +GoError cQueryExternal(querier_t *ptr, uint64_t gas_limit, uint64_t *used_gas, U8SliceView request, UnmanagedVector *result, UnmanagedVector *errOut); + +// Gateway functions (db) +GoError cGet_cgo(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView key, UnmanagedVector *val, UnmanagedVector *errOut) { + return cGet(ptr, gas_meter, used_gas, key, val, errOut); +} +GoError cSet_cgo(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView key, U8SliceView val, UnmanagedVector *errOut) { + return cSet(ptr, gas_meter, used_gas, key, val, errOut); +} +GoError cDelete_cgo(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView key, UnmanagedVector *errOut) { + return cDelete(ptr, gas_meter, used_gas, key, errOut); +} +GoError cScan_cgo(db_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, U8SliceView start, U8SliceView end, int32_t order, GoIter *out, UnmanagedVector *errOut) { + return cScan(ptr, gas_meter, used_gas, start, end, order, out, errOut); +} + +// Gateway functions (iterator) +GoError cNext_cgo(iterator_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, UnmanagedVector *key, UnmanagedVector *val, UnmanagedVector *errOut) { + return cNext(ptr, gas_meter, used_gas, key, val, errOut); +} +GoError cNextKey_cgo(iterator_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, UnmanagedVector *key, UnmanagedVector *errOut) { + return cNextKey(ptr, gas_meter, used_gas, key, errOut); +} +GoError cNextValue_cgo(iterator_t *ptr, gas_meter_t *gas_meter, uint64_t *used_gas, UnmanagedVector *val, UnmanagedVector *errOut) { + return cNextValue(ptr, gas_meter, used_gas, val, errOut); +} + +// Gateway functions (api) +GoError cCanonicalAddress_cgo(api_t *ptr, U8SliceView src, UnmanagedVector *dest, UnmanagedVector *errOut, uint64_t *used_gas) { + return cCanonicalAddress(ptr, src, dest, errOut, used_gas); +} +GoError cHumanAddress_cgo(api_t *ptr, U8SliceView src, UnmanagedVector *dest, UnmanagedVector *errOut, uint64_t *used_gas) { + return cHumanAddress(ptr, src, dest, errOut, used_gas); +} + +// Gateway functions (querier) +GoError cQueryExternal_cgo(querier_t *ptr, uint64_t gas_limit, uint64_t *used_gas, U8SliceView request, UnmanagedVector *result, UnmanagedVector *errOut) { + return cQueryExternal(ptr, gas_limit, used_gas, request, result, errOut); +} +*/ +import "C" + +// We need these gateway functions to allow calling back to a go function from the c code. +// At least I didn't discover a cleaner way. +// Also, this needs to be in a different file than `callbacks.go`, as we cannot create functions +// in the same file that has //export directives. Only import header types diff --git a/x/wasm/artifacts/v152/api/iterator.go b/x/wasm/artifacts/v152/api/iterator.go new file mode 100644 index 0000000..a29b1d0 --- /dev/null +++ b/x/wasm/artifacts/v152/api/iterator.go @@ -0,0 +1,91 @@ +package api + +import ( + "fmt" + "sync" + + "github.com/CosmWasm/wasmvm/types" +) + +// frame stores all Iterators for one contract call +type frame []types.Iterator + +// iteratorFrames contains one frame for each contract call, indexed by contract call ID. +var ( + iteratorFrames = make(map[uint64]frame) + iteratorFramesMutex sync.Mutex +) + +// this is a global counter for creating call IDs +var ( + latestCallID uint64 + latestCallIDMutex sync.Mutex +) + +// startCall is called at the beginning of a contract call to create a new frame in iteratorFrames. +// It updates latestCallID for generating a new call ID. +func startCall() uint64 { + latestCallIDMutex.Lock() + defer latestCallIDMutex.Unlock() + latestCallID += 1 + return latestCallID +} + +// removeFrame removes the frame with for the given call ID. +// The result can be nil when the frame is not initialized, +// i.e. when startCall() is called but no iterator is stored. +func removeFrame(callID uint64) frame { + iteratorFramesMutex.Lock() + defer iteratorFramesMutex.Unlock() + + remove := iteratorFrames[callID] + delete(iteratorFrames, callID) + return remove +} + +// endCall is called at the end of a contract call to remove one item the iteratorFrames +func endCall(callID uint64) { + // we pull removeFrame in another function so we don't hold the mutex while cleaning up the removed frame + remove := removeFrame(callID) + // free all iterators in the frame when we release it + for _, iter := range remove { + iter.Close() + } +} + +// storeIterator will add this to the end of the frame for the given ID and return a reference to it. +// We start counting with 1, so the 0 value is flagged as an error. This means we must +// remember to do idx-1 when retrieving +func storeIterator(callID uint64, it types.Iterator, frameLenLimit int) (uint64, error) { + iteratorFramesMutex.Lock() + defer iteratorFramesMutex.Unlock() + + old_frame_len := len(iteratorFrames[callID]) + if old_frame_len >= frameLenLimit { + return 0, fmt.Errorf("Reached iterator limit (%d)", frameLenLimit) + } + + // store at array position `old_frame_len` + iteratorFrames[callID] = append(iteratorFrames[callID], it) + new_index := old_frame_len + 1 + + return uint64(new_index), nil +} + +// retrieveIterator will recover an iterator based on index. This ensures it will not be garbage collected. +// We start counting with 1, in storeIterator so the 0 value is flagged as an error. This means we must +// remember to do idx-1 when retrieving +func retrieveIterator(callID uint64, index uint64) types.Iterator { + iteratorFramesMutex.Lock() + defer iteratorFramesMutex.Unlock() + myFrame := iteratorFrames[callID] + if myFrame == nil { + return nil + } + posInFrame := int(index) - 1 + if posInFrame < 0 || posInFrame >= len(myFrame) { + // index out of range + return nil + } + return myFrame[posInFrame] +} diff --git a/x/wasm/artifacts/v152/api/iterator_test.go b/x/wasm/artifacts/v152/api/iterator_test.go new file mode 100644 index 0000000..f548c82 --- /dev/null +++ b/x/wasm/artifacts/v152/api/iterator_test.go @@ -0,0 +1,290 @@ +package api + +import ( + "encoding/json" + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/CosmWasm/wasmd/x/wasm/artifacts/v152/api/testdb" + "github.com/CosmWasm/wasmvm/types" +) + +type queueData struct { + checksum []byte + store *Lookup + api *types.GoAPI + querier types.Querier +} + +func (q queueData) Store(meter MockGasMeter) types.KVStore { + return q.store.WithGasMeter(meter) +} + +func setupQueueContractWithData(t *testing.T, cache Cache, values ...int) queueData { + checksum := createQueueContract(t, cache) + + gasMeter1 := NewMockGasMeter(TESTING_GAS_LIMIT) + // instantiate it with this store + store := NewLookup(gasMeter1) + api := NewMockAPI() + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, types.Coins{types.NewCoin(100, "ATOM")}) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + msg := []byte(`{}`) + + igasMeter1 := types.GasMeter(gasMeter1) + res, _, err := Instantiate(cache, checksum, env, info, msg, &igasMeter1, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) + + for _, value := range values { + // push 17 + var gasMeter2 types.GasMeter = NewMockGasMeter(TESTING_GAS_LIMIT) + push := []byte(fmt.Sprintf(`{"enqueue":{"value":%d}}`, value)) + res, _, err = Execute(cache, checksum, env, info, push, &gasMeter2, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) + } + + return queueData{ + checksum: checksum, + store: store, + api: api, + querier: querier, + } +} + +func setupQueueContract(t *testing.T, cache Cache) queueData { + return setupQueueContractWithData(t, cache, 17, 22) +} + +func TestStoreIterator(t *testing.T) { + const limit = 2000 + callID1 := startCall() + callID2 := startCall() + + store := testdb.NewMemDB() + var iter types.Iterator + var index uint64 + var err error + + iter, _ = store.Iterator(nil, nil) + index, err = storeIterator(callID1, iter, limit) + require.NoError(t, err) + require.Equal(t, uint64(1), index) + iter, _ = store.Iterator(nil, nil) + index, err = storeIterator(callID1, iter, limit) + require.NoError(t, err) + require.Equal(t, uint64(2), index) + + iter, _ = store.Iterator(nil, nil) + index, err = storeIterator(callID2, iter, limit) + require.NoError(t, err) + require.Equal(t, uint64(1), index) + iter, _ = store.Iterator(nil, nil) + index, err = storeIterator(callID2, iter, limit) + require.NoError(t, err) + require.Equal(t, uint64(2), index) + iter, _ = store.Iterator(nil, nil) + index, err = storeIterator(callID2, iter, limit) + require.NoError(t, err) + require.Equal(t, uint64(3), index) + + endCall(callID1) + endCall(callID2) +} + +func TestStoreIteratorHitsLimit(t *testing.T) { + callID := startCall() + + store := testdb.NewMemDB() + var iter types.Iterator + var err error + const limit = 2 + + iter, _ = store.Iterator(nil, nil) + _, err = storeIterator(callID, iter, limit) + require.NoError(t, err) + + iter, _ = store.Iterator(nil, nil) + _, err = storeIterator(callID, iter, limit) + require.NoError(t, err) + + iter, _ = store.Iterator(nil, nil) + _, err = storeIterator(callID, iter, limit) + require.ErrorContains(t, err, "Reached iterator limit (2)") + + endCall(callID) +} + +func TestRetrieveIterator(t *testing.T) { + const limit = 2000 + callID1 := startCall() + callID2 := startCall() + + store := testdb.NewMemDB() + var iter types.Iterator + var err error + + iter, _ = store.Iterator(nil, nil) + index11, err := storeIterator(callID1, iter, limit) + require.NoError(t, err) + iter, _ = store.Iterator(nil, nil) + _, err = storeIterator(callID1, iter, limit) + require.NoError(t, err) + iter, _ = store.Iterator(nil, nil) + _, err = storeIterator(callID2, iter, limit) + require.NoError(t, err) + iter, _ = store.Iterator(nil, nil) + index22, err := storeIterator(callID2, iter, limit) + require.NoError(t, err) + iter, err = store.Iterator(nil, nil) + require.NoError(t, err) + index23, err := storeIterator(callID2, iter, limit) + require.NoError(t, err) + + // Retrieve existing + iter = retrieveIterator(callID1, index11) + require.NotNil(t, iter) + iter = retrieveIterator(callID2, index22) + require.NotNil(t, iter) + + // Retrieve non-existent index + iter = retrieveIterator(callID1, index23) + require.Nil(t, iter) + iter = retrieveIterator(callID1, uint64(0)) + require.Nil(t, iter) + + // Retrieve non-existent call ID + iter = retrieveIterator(callID1+1_234_567, index23) + require.Nil(t, iter) + + endCall(callID1) + endCall(callID2) +} + +func TestQueueIteratorSimple(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + + setup := setupQueueContract(t, cache) + checksum, querier, api := setup.checksum, setup.querier, setup.api + + // query the sum + gasMeter := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter := types.GasMeter(gasMeter) + store := setup.Store(gasMeter) + query := []byte(`{"sum":{}}`) + env := MockEnvBin(t) + data, _, err := Query(cache, checksum, env, query, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + var qres types.QueryResponse + err = json.Unmarshal(data, &qres) + require.NoError(t, err) + require.Equal(t, "", qres.Err) + require.Equal(t, `{"sum":39}`, string(qres.Ok)) + + // query reduce (multiple iterators at once) + query = []byte(`{"reducer":{}}`) + data, _, err = Query(cache, checksum, env, query, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + var reduced types.QueryResponse + err = json.Unmarshal(data, &reduced) + require.NoError(t, err) + require.Equal(t, "", reduced.Err) + require.Equal(t, `{"counters":[[17,22],[22,0]]}`, string(reduced.Ok)) +} + +func TestQueueIteratorRaces(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + + assert.Equal(t, 0, len(iteratorFrames)) + + contract1 := setupQueueContractWithData(t, cache, 17, 22) + contract2 := setupQueueContractWithData(t, cache, 1, 19, 6, 35, 8) + contract3 := setupQueueContractWithData(t, cache, 11, 6, 2) + env := MockEnvBin(t) + + reduceQuery := func(t *testing.T, setup queueData, expected string) { + checksum, querier, api := setup.checksum, setup.querier, setup.api + gasMeter := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter := types.GasMeter(gasMeter) + store := setup.Store(gasMeter) + + // query reduce (multiple iterators at once) + query := []byte(`{"reducer":{}}`) + data, _, err := Query(cache, checksum, env, query, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + var reduced types.QueryResponse + err = json.Unmarshal(data, &reduced) + require.NoError(t, err) + require.Equal(t, "", reduced.Err) + require.Equal(t, fmt.Sprintf(`{"counters":%s}`, expected), string(reduced.Ok)) + } + + // 30 concurrent batches (in go routines) to trigger any race condition + numBatches := 30 + + var wg sync.WaitGroup + // for each batch, query each of the 3 contracts - so the contract queries get mixed together + wg.Add(numBatches * 3) + for i := 0; i < numBatches; i++ { + go func() { + reduceQuery(t, contract1, "[[17,22],[22,0]]") + wg.Done() + }() + go func() { + reduceQuery(t, contract2, "[[1,68],[19,35],[6,62],[35,0],[8,54]]") + wg.Done() + }() + go func() { + reduceQuery(t, contract3, "[[11,0],[6,11],[2,17]]") + wg.Done() + }() + } + wg.Wait() + + // when they finish, we should have removed all frames + assert.Equal(t, 0, len(iteratorFrames)) +} + +func TestQueueIteratorLimit(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + + setup := setupQueueContract(t, cache) + checksum, querier, api := setup.checksum, setup.querier, setup.api + + var err error + var qres types.QueryResponse + var gasLimit uint64 + + // Open 5000 iterators + gasLimit = TESTING_GAS_LIMIT + gasMeter := NewMockGasMeter(gasLimit) + igasMeter := types.GasMeter(gasMeter) + store := setup.Store(gasMeter) + query := []byte(`{"open_iterators":{"count":5000}}`) + env := MockEnvBin(t) + data, _, err := Query(cache, checksum, env, query, &igasMeter, store, api, &querier, gasLimit, TESTING_PRINT_DEBUG) + require.NoError(t, err) + err = json.Unmarshal(data, &qres) + require.NoError(t, err) + require.Equal(t, "", qres.Err) + require.Equal(t, `{}`, string(qres.Ok)) + + // Open 35000 iterators + gasLimit = TESTING_GAS_LIMIT * 4 + gasMeter = NewMockGasMeter(gasLimit) + igasMeter = types.GasMeter(gasMeter) + store = setup.Store(gasMeter) + query = []byte(`{"open_iterators":{"count":35000}}`) + env = MockEnvBin(t) + _, _, err = Query(cache, checksum, env, query, &igasMeter, store, api, &querier, gasLimit, TESTING_PRINT_DEBUG) + require.ErrorContains(t, err, "Reached iterator limit (32768)") +} diff --git a/x/wasm/artifacts/v152/api/lib.go b/x/wasm/artifacts/v152/api/lib.go new file mode 100644 index 0000000..b738743 --- /dev/null +++ b/x/wasm/artifacts/v152/api/lib.go @@ -0,0 +1,634 @@ +package api + +// #include +// #include "bindings.h" +import "C" + +import ( + "fmt" + "runtime" + "syscall" + + "github.com/CosmWasm/wasmvm/types" +) + +// Value types +type ( + cint = C.int + cbool = C.bool + cusize = C.size_t + cu8 = C.uint8_t + cu32 = C.uint32_t + cu64 = C.uint64_t + ci8 = C.int8_t + ci32 = C.int32_t + ci64 = C.int64_t +) + +// Pointers +type ( + cu8_ptr = *C.uint8_t +) + +type Cache struct { + ptr *C.cache_t +} + +type Querier = types.Querier + +func InitCache(dataDir string, supportedCapabilities string, cacheSize uint32, instanceMemoryLimit uint32) (Cache, error) { + dataDirBytes := []byte(dataDir) + supportedCapabilitiesBytes := []byte(supportedCapabilities) + + d := makeView(dataDirBytes) + defer runtime.KeepAlive(dataDirBytes) + capabilitiesView := makeView(supportedCapabilitiesBytes) + defer runtime.KeepAlive(supportedCapabilitiesBytes) + + errmsg := uninitializedUnmanagedVector() + + ptr, err := C.init_cache(d, capabilitiesView, cu32(cacheSize), cu32(instanceMemoryLimit), &errmsg) + if err != nil { + return Cache{}, errorWithMessage(err, errmsg) + } + return Cache{ptr: ptr}, nil +} + +func ReleaseCache(cache Cache) { + C.release_cache(cache.ptr) +} + +func StoreCode(cache Cache, wasm []byte) ([]byte, error) { + w := makeView(wasm) + defer runtime.KeepAlive(wasm) + errmsg := uninitializedUnmanagedVector() + checksum, err := C.save_wasm(cache.ptr, w, cbool(false), &errmsg) + if err != nil { + return nil, errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(checksum), nil +} + +func StoreCodeUnchecked(cache Cache, wasm []byte) ([]byte, error) { + w := makeView(wasm) + defer runtime.KeepAlive(wasm) + errmsg := uninitializedUnmanagedVector() + checksum, err := C.save_wasm(cache.ptr, w, cbool(true), &errmsg) + if err != nil { + return nil, errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(checksum), nil +} + +func RemoveCode(cache Cache, checksum []byte) error { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + errmsg := uninitializedUnmanagedVector() + _, err := C.remove_wasm(cache.ptr, cs, &errmsg) + if err != nil { + return errorWithMessage(err, errmsg) + } + return nil +} + +func GetCode(cache Cache, checksum []byte) ([]byte, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + errmsg := uninitializedUnmanagedVector() + wasm, err := C.load_wasm(cache.ptr, cs, &errmsg) + if err != nil { + return nil, errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(wasm), nil +} + +func Pin(cache Cache, checksum []byte) error { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + errmsg := uninitializedUnmanagedVector() + _, err := C.pin(cache.ptr, cs, &errmsg) + if err != nil { + return errorWithMessage(err, errmsg) + } + return nil +} + +func Unpin(cache Cache, checksum []byte) error { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + errmsg := uninitializedUnmanagedVector() + _, err := C.unpin(cache.ptr, cs, &errmsg) + if err != nil { + return errorWithMessage(err, errmsg) + } + return nil +} + +func AnalyzeCode(cache Cache, checksum []byte) (*types.AnalysisReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + errmsg := uninitializedUnmanagedVector() + report, err := C.analyze_code(cache.ptr, cs, &errmsg) + if err != nil { + return nil, errorWithMessage(err, errmsg) + } + requiredCapabilities := string(copyAndDestroyUnmanagedVector(report.required_capabilities)) + res := types.AnalysisReport{ + HasIBCEntryPoints: bool(report.has_ibc_entry_points), + RequiredFeatures: requiredCapabilities, + RequiredCapabilities: requiredCapabilities, + } + return &res, nil +} + +func GetMetrics(cache Cache) (*types.Metrics, error) { + errmsg := uninitializedUnmanagedVector() + metrics, err := C.get_metrics(cache.ptr, &errmsg) + if err != nil { + return nil, errorWithMessage(err, errmsg) + } + + return &types.Metrics{ + HitsPinnedMemoryCache: uint32(metrics.hits_pinned_memory_cache), + HitsMemoryCache: uint32(metrics.hits_memory_cache), + HitsFsCache: uint32(metrics.hits_fs_cache), + Misses: uint32(metrics.misses), + ElementsPinnedMemoryCache: uint64(metrics.elements_pinned_memory_cache), + ElementsMemoryCache: uint64(metrics.elements_memory_cache), + SizePinnedMemoryCache: uint64(metrics.size_pinned_memory_cache), + SizeMemoryCache: uint64(metrics.size_memory_cache), + }, nil +} + +func Instantiate( + cache Cache, + checksum []byte, + env []byte, + info []byte, + msg []byte, + gasMeter *types.GasMeter, + store types.KVStore, + api *types.GoAPI, + querier *Querier, + gasLimit uint64, + printDebug bool, +) ([]byte, types.GasReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + e := makeView(env) + defer runtime.KeepAlive(env) + i := makeView(info) + defer runtime.KeepAlive(info) + m := makeView(msg) + defer runtime.KeepAlive(msg) + + callID := startCall() + defer endCall(callID) + + dbState := buildDBState(store, callID) + db := buildDB(&dbState, gasMeter) + a := buildAPI(api) + q := buildQuerier(querier) + var gasReport C.GasReport + errmsg := uninitializedUnmanagedVector() + + res, err := C.instantiate(cache.ptr, cs, e, i, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + +func Execute( + cache Cache, + checksum []byte, + env []byte, + info []byte, + msg []byte, + gasMeter *types.GasMeter, + store types.KVStore, + api *types.GoAPI, + querier *Querier, + gasLimit uint64, + printDebug bool, +) ([]byte, types.GasReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + e := makeView(env) + defer runtime.KeepAlive(env) + i := makeView(info) + defer runtime.KeepAlive(info) + m := makeView(msg) + defer runtime.KeepAlive(msg) + + callID := startCall() + defer endCall(callID) + + dbState := buildDBState(store, callID) + db := buildDB(&dbState, gasMeter) + a := buildAPI(api) + q := buildQuerier(querier) + var gasReport C.GasReport + errmsg := uninitializedUnmanagedVector() + + res, err := C.execute(cache.ptr, cs, e, i, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + +func Migrate( + cache Cache, + checksum []byte, + env []byte, + msg []byte, + gasMeter *types.GasMeter, + store types.KVStore, + api *types.GoAPI, + querier *Querier, + gasLimit uint64, + printDebug bool, +) ([]byte, types.GasReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + e := makeView(env) + defer runtime.KeepAlive(env) + m := makeView(msg) + defer runtime.KeepAlive(msg) + + callID := startCall() + defer endCall(callID) + + dbState := buildDBState(store, callID) + db := buildDB(&dbState, gasMeter) + a := buildAPI(api) + q := buildQuerier(querier) + var gasReport C.GasReport + errmsg := uninitializedUnmanagedVector() + + res, err := C.migrate(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + +func Sudo( + cache Cache, + checksum []byte, + env []byte, + msg []byte, + gasMeter *types.GasMeter, + store types.KVStore, + api *types.GoAPI, + querier *Querier, + gasLimit uint64, + printDebug bool, +) ([]byte, types.GasReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + e := makeView(env) + defer runtime.KeepAlive(env) + m := makeView(msg) + defer runtime.KeepAlive(msg) + + callID := startCall() + defer endCall(callID) + + dbState := buildDBState(store, callID) + db := buildDB(&dbState, gasMeter) + a := buildAPI(api) + q := buildQuerier(querier) + var gasReport C.GasReport + errmsg := uninitializedUnmanagedVector() + + res, err := C.sudo(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + +func Reply( + cache Cache, + checksum []byte, + env []byte, + reply []byte, + gasMeter *types.GasMeter, + store types.KVStore, + api *types.GoAPI, + querier *Querier, + gasLimit uint64, + printDebug bool, +) ([]byte, types.GasReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + e := makeView(env) + defer runtime.KeepAlive(env) + r := makeView(reply) + defer runtime.KeepAlive(reply) + + callID := startCall() + defer endCall(callID) + + dbState := buildDBState(store, callID) + db := buildDB(&dbState, gasMeter) + a := buildAPI(api) + q := buildQuerier(querier) + var gasReport C.GasReport + errmsg := uninitializedUnmanagedVector() + + res, err := C.reply(cache.ptr, cs, e, r, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + +func Query( + cache Cache, + checksum []byte, + env []byte, + msg []byte, + gasMeter *types.GasMeter, + store types.KVStore, + api *types.GoAPI, + querier *Querier, + gasLimit uint64, + printDebug bool, +) ([]byte, types.GasReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + e := makeView(env) + defer runtime.KeepAlive(env) + m := makeView(msg) + defer runtime.KeepAlive(msg) + + callID := startCall() + defer endCall(callID) + + dbState := buildDBState(store, callID) + db := buildDB(&dbState, gasMeter) + a := buildAPI(api) + q := buildQuerier(querier) + var gasReport C.GasReport + errmsg := uninitializedUnmanagedVector() + + res, err := C.query(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + +func IBCChannelOpen( + cache Cache, + checksum []byte, + env []byte, + msg []byte, + gasMeter *types.GasMeter, + store types.KVStore, + api *types.GoAPI, + querier *Querier, + gasLimit uint64, + printDebug bool, +) ([]byte, types.GasReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + e := makeView(env) + defer runtime.KeepAlive(env) + m := makeView(msg) + defer runtime.KeepAlive(msg) + + callID := startCall() + defer endCall(callID) + + dbState := buildDBState(store, callID) + db := buildDB(&dbState, gasMeter) + a := buildAPI(api) + q := buildQuerier(querier) + var gasReport C.GasReport + errmsg := uninitializedUnmanagedVector() + + res, err := C.ibc_channel_open(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + +func IBCChannelConnect( + cache Cache, + checksum []byte, + env []byte, + msg []byte, + gasMeter *types.GasMeter, + store types.KVStore, + api *types.GoAPI, + querier *Querier, + gasLimit uint64, + printDebug bool, +) ([]byte, types.GasReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + e := makeView(env) + defer runtime.KeepAlive(env) + m := makeView(msg) + defer runtime.KeepAlive(msg) + + callID := startCall() + defer endCall(callID) + + dbState := buildDBState(store, callID) + db := buildDB(&dbState, gasMeter) + a := buildAPI(api) + q := buildQuerier(querier) + var gasReport C.GasReport + errmsg := uninitializedUnmanagedVector() + + res, err := C.ibc_channel_connect(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + +func IBCChannelClose( + cache Cache, + checksum []byte, + env []byte, + msg []byte, + gasMeter *types.GasMeter, + store types.KVStore, + api *types.GoAPI, + querier *Querier, + gasLimit uint64, + printDebug bool, +) ([]byte, types.GasReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + e := makeView(env) + defer runtime.KeepAlive(env) + m := makeView(msg) + defer runtime.KeepAlive(msg) + + callID := startCall() + defer endCall(callID) + + dbState := buildDBState(store, callID) + db := buildDB(&dbState, gasMeter) + a := buildAPI(api) + q := buildQuerier(querier) + var gasReport C.GasReport + errmsg := uninitializedUnmanagedVector() + + res, err := C.ibc_channel_close(cache.ptr, cs, e, m, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + +func IBCPacketReceive( + cache Cache, + checksum []byte, + env []byte, + packet []byte, + gasMeter *types.GasMeter, + store types.KVStore, + api *types.GoAPI, + querier *Querier, + gasLimit uint64, + printDebug bool, +) ([]byte, types.GasReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + e := makeView(env) + defer runtime.KeepAlive(env) + pa := makeView(packet) + defer runtime.KeepAlive(packet) + + callID := startCall() + defer endCall(callID) + + dbState := buildDBState(store, callID) + db := buildDB(&dbState, gasMeter) + a := buildAPI(api) + q := buildQuerier(querier) + var gasReport C.GasReport + errmsg := uninitializedUnmanagedVector() + + res, err := C.ibc_packet_receive(cache.ptr, cs, e, pa, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + +func IBCPacketAck( + cache Cache, + checksum []byte, + env []byte, + ack []byte, + gasMeter *types.GasMeter, + store types.KVStore, + api *types.GoAPI, + querier *Querier, + gasLimit uint64, + printDebug bool, +) ([]byte, types.GasReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + e := makeView(env) + defer runtime.KeepAlive(env) + ac := makeView(ack) + defer runtime.KeepAlive(ack) + + callID := startCall() + defer endCall(callID) + + dbState := buildDBState(store, callID) + db := buildDB(&dbState, gasMeter) + a := buildAPI(api) + q := buildQuerier(querier) + var gasReport C.GasReport + errmsg := uninitializedUnmanagedVector() + + res, err := C.ibc_packet_ack(cache.ptr, cs, e, ac, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + +func IBCPacketTimeout( + cache Cache, + checksum []byte, + env []byte, + packet []byte, + gasMeter *types.GasMeter, + store types.KVStore, + api *types.GoAPI, + querier *Querier, + gasLimit uint64, + printDebug bool, +) ([]byte, types.GasReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + e := makeView(env) + defer runtime.KeepAlive(env) + pa := makeView(packet) + defer runtime.KeepAlive(packet) + + callID := startCall() + defer endCall(callID) + + dbState := buildDBState(store, callID) + db := buildDB(&dbState, gasMeter) + a := buildAPI(api) + q := buildQuerier(querier) + var gasReport C.GasReport + errmsg := uninitializedUnmanagedVector() + + res, err := C.ibc_packet_timeout(cache.ptr, cs, e, pa, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + +func convertGasReport(report C.GasReport) types.GasReport { + return types.GasReport{ + Limit: uint64(report.limit), + Remaining: uint64(report.remaining), + UsedExternally: uint64(report.used_externally), + UsedInternally: uint64(report.used_internally), + } +} + +/**** To error module ***/ + +func errorWithMessage(err error, b C.UnmanagedVector) error { + // this checks for out of gas as a special case + if errno, ok := err.(syscall.Errno); ok && int(errno) == 2 { + return types.OutOfGasError{} + } + msg := copyAndDestroyUnmanagedVector(b) + if msg == nil { + return err + } + return fmt.Errorf("%s", string(msg)) +} diff --git a/x/wasm/artifacts/v152/api/lib_test.go b/x/wasm/artifacts/v152/api/lib_test.go new file mode 100644 index 0000000..9ab03c1 --- /dev/null +++ b/x/wasm/artifacts/v152/api/lib_test.go @@ -0,0 +1,1160 @@ +package api + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/CosmWasm/wasmvm/types" +) + +const ( + TESTING_CAPABILITIES = "staking,stargate,iterator,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3" + TESTING_PRINT_DEBUG = false + TESTING_GAS_LIMIT = uint64(500_000_000_000) // ~0.5ms + TESTING_MEMORY_LIMIT = 32 // MiB + TESTING_CACHE_SIZE = 100 // MiB +) + +func TestInitAndReleaseCache(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "wasmvm-testing") + require.NoError(t, err) + defer os.RemoveAll(tmpdir) + + cache, err := InitCache(tmpdir, TESTING_CAPABILITIES, TESTING_CACHE_SIZE, TESTING_MEMORY_LIMIT) + require.NoError(t, err) + ReleaseCache(cache) +} + +// wasmd expects us to create the base directory +// https://github.com/CosmWasm/wasmd/blob/v0.30.0/x/wasm/keeper/keeper.go#L128 +func TestInitCacheWorksForNonExistentDir(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "wasmvm-testing") + require.NoError(t, err) + defer os.RemoveAll(tmpdir) + + createMe := filepath.Join(tmpdir, "does-not-yet-exist") + cache, err := InitCache(createMe, TESTING_CAPABILITIES, TESTING_CACHE_SIZE, TESTING_MEMORY_LIMIT) + require.NoError(t, err) + ReleaseCache(cache) +} + +func TestInitCacheErrorsForBrokenDir(t *testing.T) { + // Use colon to make this fail on Windows + // https://gist.github.com/doctaphred/d01d05291546186941e1b7ddc02034d3 + // On Unix we should not have permission to create this. + cannotBeCreated := "/foo:bar" + _, err := InitCache(cannotBeCreated, TESTING_CAPABILITIES, TESTING_CACHE_SIZE, TESTING_MEMORY_LIMIT) + require.ErrorContains(t, err, "Error creating state directory") +} + +func TestInitCacheEmptyCapabilities(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "wasmvm-testing") + require.NoError(t, err) + defer os.RemoveAll(tmpdir) + cache, err := InitCache(tmpdir, "", TESTING_CACHE_SIZE, TESTING_MEMORY_LIMIT) + require.NoError(t, err) + ReleaseCache(cache) +} + +func withCache(t *testing.T) (Cache, func()) { + tmpdir, err := ioutil.TempDir("", "wasmvm-testing") + require.NoError(t, err) + cache, err := InitCache(tmpdir, TESTING_CAPABILITIES, TESTING_CACHE_SIZE, TESTING_MEMORY_LIMIT) + require.NoError(t, err) + + cleanup := func() { + os.RemoveAll(tmpdir) + ReleaseCache(cache) + } + return cache, cleanup +} + +func TestStoreCodeAndGetCode(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + + wasm, err := ioutil.ReadFile("../../testdata/hackatom.wasm") + require.NoError(t, err) + + checksum, err := StoreCode(cache, wasm) + require.NoError(t, err) + expectedChecksum := sha256.Sum256(wasm) + require.Equal(t, expectedChecksum[:], checksum) + + code, err := GetCode(cache, checksum) + require.NoError(t, err) + require.Equal(t, wasm, code) +} + +func TestRemoveCode(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + + wasm, err := ioutil.ReadFile("../../testdata/hackatom.wasm") + require.NoError(t, err) + + checksum, err := StoreCode(cache, wasm) + require.NoError(t, err) + + // First removal works + err = RemoveCode(cache, checksum) + require.NoError(t, err) + + // Second removal fails + err = RemoveCode(cache, checksum) + require.ErrorContains(t, err, "Wasm file does not exist") +} + +func TestStoreCodeFailsWithBadData(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + + wasm := []byte("some invalid data") + _, err := StoreCode(cache, wasm) + require.Error(t, err) +} + +func TestStoreCodeUnchecked(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + + wasm, err := ioutil.ReadFile("../../testdata/hackatom.wasm") + require.NoError(t, err) + + checksum, err := StoreCodeUnchecked(cache, wasm) + require.NoError(t, err) + expectedChecksum := sha256.Sum256(wasm) + require.Equal(t, expectedChecksum[:], checksum) + + code, err := GetCode(cache, checksum) + require.NoError(t, err) + require.Equal(t, wasm, code) +} + +func TestPin(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + + wasm, err := ioutil.ReadFile("../../testdata/hackatom.wasm") + require.NoError(t, err) + + checksum, err := StoreCode(cache, wasm) + require.NoError(t, err) + + err = Pin(cache, checksum) + require.NoError(t, err) + + // Can be called again with no effect + err = Pin(cache, checksum) + require.NoError(t, err) +} + +func TestPinErrors(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + var err error + + // Nil checksum (errors in wasmvm Rust code) + var nilChecksum []byte + err = Pin(cache, nilChecksum) + require.ErrorContains(t, err, "Null/Nil argument: checksum") + + // Checksum too short (errors in wasmvm Rust code) + brokenChecksum := []byte{0x3f, 0xd7, 0x5a, 0x76} + err = Pin(cache, brokenChecksum) + require.ErrorContains(t, err, "Checksum not of length 32") + + // Unknown checksum (errors in cosmwasm-vm) + unknownChecksum := []byte{ + 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, + } + err = Pin(cache, unknownChecksum) + require.ErrorContains(t, err, "Error opening Wasm file for reading") +} + +func TestUnpin(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + + wasm, err := ioutil.ReadFile("../../testdata/hackatom.wasm") + require.NoError(t, err) + + checksum, err := StoreCode(cache, wasm) + require.NoError(t, err) + + err = Pin(cache, checksum) + require.NoError(t, err) + + err = Unpin(cache, checksum) + require.NoError(t, err) + + // Can be called again with no effect + err = Unpin(cache, checksum) + require.NoError(t, err) +} + +func TestUnpinErrors(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + var err error + + // Nil checksum (errors in wasmvm Rust code) + var nilChecksum []byte + err = Unpin(cache, nilChecksum) + require.ErrorContains(t, err, "Null/Nil argument: checksum") + + // Checksum too short (errors in wasmvm Rust code) + brokenChecksum := []byte{0x3f, 0xd7, 0x5a, 0x76} + err = Unpin(cache, brokenChecksum) + require.ErrorContains(t, err, "Checksum not of length 32") + + // No error case triggered in cosmwasm-vm is known right now +} + +func TestGetMetrics(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + + // GetMetrics 1 + metrics, err := GetMetrics(cache) + require.NoError(t, err) + assert.Equal(t, &types.Metrics{}, metrics) + + // Store contract + wasm, err := ioutil.ReadFile("../../testdata/hackatom.wasm") + require.NoError(t, err) + checksum, err := StoreCode(cache, wasm) + require.NoError(t, err) + + // GetMetrics 2 + metrics, err = GetMetrics(cache) + require.NoError(t, err) + assert.Equal(t, &types.Metrics{}, metrics) + + // Instantiate 1 + gasMeter := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter := types.GasMeter(gasMeter) + store := NewLookup(gasMeter) + api := NewMockAPI() + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, types.Coins{types.NewCoin(100, "ATOM")}) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + msg1 := []byte(`{"verifier": "fred", "beneficiary": "bob"}`) + _, _, err = Instantiate(cache, checksum, env, info, msg1, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + + // GetMetrics 3 + metrics, err = GetMetrics(cache) + assert.NoError(t, err) + require.Equal(t, uint32(0), metrics.HitsMemoryCache) + require.Equal(t, uint32(1), metrics.HitsFsCache) + require.Equal(t, uint64(1), metrics.ElementsMemoryCache) + t.Log(t, metrics.SizeMemoryCache) + require.InEpsilon(t, 2832576, metrics.SizeMemoryCache, 0.25) + + // Instantiate 2 + msg2 := []byte(`{"verifier": "fred", "beneficiary": "susi"}`) + _, _, err = Instantiate(cache, checksum, env, info, msg2, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + + // GetMetrics 4 + metrics, err = GetMetrics(cache) + assert.NoError(t, err) + require.Equal(t, uint32(1), metrics.HitsMemoryCache) + require.Equal(t, uint32(1), metrics.HitsFsCache) + require.Equal(t, uint64(1), metrics.ElementsMemoryCache) + require.InEpsilon(t, 2832576, metrics.SizeMemoryCache, 0.25) + + // Pin + err = Pin(cache, checksum) + require.NoError(t, err) + + // GetMetrics 5 + metrics, err = GetMetrics(cache) + assert.NoError(t, err) + require.Equal(t, uint32(1), metrics.HitsMemoryCache) + require.Equal(t, uint32(2), metrics.HitsFsCache) + require.Equal(t, uint64(1), metrics.ElementsPinnedMemoryCache) + require.Equal(t, uint64(1), metrics.ElementsMemoryCache) + require.InEpsilon(t, 2832576, metrics.SizePinnedMemoryCache, 0.25) + require.InEpsilon(t, 2832576, metrics.SizeMemoryCache, 0.25) + + // Instantiate 3 + msg3 := []byte(`{"verifier": "fred", "beneficiary": "bert"}`) + _, _, err = Instantiate(cache, checksum, env, info, msg3, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + + // GetMetrics 6 + metrics, err = GetMetrics(cache) + assert.NoError(t, err) + require.Equal(t, uint32(1), metrics.HitsPinnedMemoryCache) + require.Equal(t, uint32(1), metrics.HitsMemoryCache) + require.Equal(t, uint32(2), metrics.HitsFsCache) + require.Equal(t, uint64(1), metrics.ElementsPinnedMemoryCache) + require.Equal(t, uint64(1), metrics.ElementsMemoryCache) + require.InEpsilon(t, 2832576, metrics.SizePinnedMemoryCache, 0.25) + require.InEpsilon(t, 2832576, metrics.SizeMemoryCache, 0.25) + + // Unpin + err = Unpin(cache, checksum) + require.NoError(t, err) + + // GetMetrics 7 + metrics, err = GetMetrics(cache) + assert.NoError(t, err) + require.Equal(t, uint32(1), metrics.HitsPinnedMemoryCache) + require.Equal(t, uint32(1), metrics.HitsMemoryCache) + require.Equal(t, uint32(2), metrics.HitsFsCache) + require.Equal(t, uint64(0), metrics.ElementsPinnedMemoryCache) + require.Equal(t, uint64(1), metrics.ElementsMemoryCache) + require.Equal(t, uint64(0), metrics.SizePinnedMemoryCache) + require.InEpsilon(t, 2832576, metrics.SizeMemoryCache, 0.25) + + // Instantiate 4 + msg4 := []byte(`{"verifier": "fred", "beneficiary": "jeff"}`) + _, _, err = Instantiate(cache, checksum, env, info, msg4, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + + // GetMetrics 8 + metrics, err = GetMetrics(cache) + assert.NoError(t, err) + require.Equal(t, uint32(1), metrics.HitsPinnedMemoryCache) + require.Equal(t, uint32(2), metrics.HitsMemoryCache) + require.Equal(t, uint32(2), metrics.HitsFsCache) + require.Equal(t, uint64(0), metrics.ElementsPinnedMemoryCache) + require.Equal(t, uint64(1), metrics.ElementsMemoryCache) + require.Equal(t, uint64(0), metrics.SizePinnedMemoryCache) + require.InEpsilon(t, 2832576, metrics.SizeMemoryCache, 0.25) +} + +func TestInstantiate(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + + // create contract + wasm, err := ioutil.ReadFile("../../testdata/hackatom.wasm") + require.NoError(t, err) + checksum, err := StoreCode(cache, wasm) + require.NoError(t, err) + + gasMeter := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter := types.GasMeter(gasMeter) + // instantiate it with this store + store := NewLookup(gasMeter) + api := NewMockAPI() + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, types.Coins{types.NewCoin(100, "ATOM")}) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + msg := []byte(`{"verifier": "fred", "beneficiary": "bob"}`) + + res, cost, err := Instantiate(cache, checksum, env, info, msg, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) + assert.Equal(t, uint64(0x13a78a36c), cost.UsedInternally) + + var result types.ContractResult + err = json.Unmarshal(res, &result) + require.NoError(t, err) + require.Equal(t, "", result.Err) + require.Equal(t, 0, len(result.Ok.Messages)) +} + +func TestExecute(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + checksum := createHackatomContract(t, cache) + + gasMeter1 := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter1 := types.GasMeter(gasMeter1) + // instantiate it with this store + store := NewLookup(gasMeter1) + api := NewMockAPI() + balance := types.Coins{types.NewCoin(250, "ATOM")} + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, balance) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + + msg := []byte(`{"verifier": "fred", "beneficiary": "bob"}`) + + start := time.Now() + res, cost, err := Instantiate(cache, checksum, env, info, msg, &igasMeter1, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + diff := time.Since(start) + require.NoError(t, err) + requireOkResponse(t, res, 0) + assert.Equal(t, uint64(0x13a78a36c), cost.UsedInternally) + t.Logf("Time (%d gas): %s\n", cost.UsedInternally, diff) + + // execute with the same store + gasMeter2 := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter2 := types.GasMeter(gasMeter2) + store.SetGasMeter(gasMeter2) + env = MockEnvBin(t) + info = MockInfoBin(t, "fred") + start = time.Now() + res, cost, err = Execute(cache, checksum, env, info, []byte(`{"release":{}}`), &igasMeter2, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + diff = time.Since(start) + require.NoError(t, err) + assert.Equal(t, uint64(0x222892d70), cost.UsedInternally) + t.Logf("Time (%d gas): %s\n", cost.UsedInternally, diff) + + // make sure it read the balance properly and we got 250 atoms + var result types.ContractResult + err = json.Unmarshal(res, &result) + require.NoError(t, err) + require.Equal(t, "", result.Err) + require.Equal(t, 1, len(result.Ok.Messages)) + + // Ensure we got our custom event + assert.Equal(t, len(result.Ok.Events), 1) + ev := result.Ok.Events[0] + assert.Equal(t, ev.Type, "hackatom") + assert.Equal(t, len(ev.Attributes), 1) + assert.Equal(t, ev.Attributes[0].Key, "action") + assert.Equal(t, ev.Attributes[0].Value, "release") + + dispatch := result.Ok.Messages[0].Msg + require.NotNil(t, dispatch.Bank, "%#v", dispatch) + require.NotNil(t, dispatch.Bank.Send, "%#v", dispatch) + send := dispatch.Bank.Send + assert.Equal(t, "bob", send.ToAddress) + assert.Equal(t, balance, send.Amount) + // check the data is properly formatted + expectedData := []byte{0xF0, 0x0B, 0xAA} + assert.Equal(t, expectedData, result.Ok.Data) +} + +func TestExecutePanic(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + checksum := createCyberpunkContract(t, cache) + + maxGas := TESTING_GAS_LIMIT + gasMeter1 := NewMockGasMeter(maxGas) + igasMeter1 := types.GasMeter(gasMeter1) + // instantiate it with this store + store := NewLookup(gasMeter1) + api := NewMockAPI() + balance := types.Coins{types.NewCoin(250, "ATOM")} + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, balance) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + + res, _, err := Instantiate(cache, checksum, env, info, []byte(`{}`), &igasMeter1, store, api, &querier, maxGas, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) + + // execute a panic + gasMeter2 := NewMockGasMeter(maxGas) + igasMeter2 := types.GasMeter(gasMeter2) + store.SetGasMeter(gasMeter2) + info = MockInfoBin(t, "fred") + _, _, err = Execute(cache, checksum, env, info, []byte(`{"panic":{}}`), &igasMeter2, store, api, &querier, maxGas, TESTING_PRINT_DEBUG) + require.ErrorContains(t, err, "RuntimeError: Aborted: panicked at 'This page intentionally faulted'") +} + +func TestExecuteUnreachable(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + checksum := createCyberpunkContract(t, cache) + + maxGas := TESTING_GAS_LIMIT + gasMeter1 := NewMockGasMeter(maxGas) + igasMeter1 := types.GasMeter(gasMeter1) + // instantiate it with this store + store := NewLookup(gasMeter1) + api := NewMockAPI() + balance := types.Coins{types.NewCoin(250, "ATOM")} + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, balance) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + + res, _, err := Instantiate(cache, checksum, env, info, []byte(`{}`), &igasMeter1, store, api, &querier, maxGas, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) + + // execute a panic + gasMeter2 := NewMockGasMeter(maxGas) + igasMeter2 := types.GasMeter(gasMeter2) + store.SetGasMeter(gasMeter2) + info = MockInfoBin(t, "fred") + _, _, err = Execute(cache, checksum, env, info, []byte(`{"unreachable":{}}`), &igasMeter2, store, api, &querier, maxGas, TESTING_PRINT_DEBUG) + require.ErrorContains(t, err, "RuntimeError: unreachable") +} + +func TestExecuteCpuLoop(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + checksum := createCyberpunkContract(t, cache) + + gasMeter1 := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter1 := types.GasMeter(gasMeter1) + // instantiate it with this store + store := NewLookup(gasMeter1) + api := NewMockAPI() + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, nil) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + + msg := []byte(`{}`) + + start := time.Now() + res, cost, err := Instantiate(cache, checksum, env, info, msg, &igasMeter1, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + diff := time.Since(start) + require.NoError(t, err) + requireOkResponse(t, res, 0) + assert.Equal(t, uint64(0xd45091d0), cost.UsedInternally) + t.Logf("Time (%d gas): %s\n", cost.UsedInternally, diff) + + // execute a cpu loop + maxGas := uint64(40_000_000) + gasMeter2 := NewMockGasMeter(maxGas) + igasMeter2 := types.GasMeter(gasMeter2) + store.SetGasMeter(gasMeter2) + info = MockInfoBin(t, "fred") + start = time.Now() + _, cost, err = Execute(cache, checksum, env, info, []byte(`{"cpu_loop":{}}`), &igasMeter2, store, api, &querier, maxGas, TESTING_PRINT_DEBUG) + diff = time.Since(start) + require.Error(t, err) + assert.Equal(t, cost.UsedInternally, maxGas) + t.Logf("CPULoop Time (%d gas): %s\n", cost.UsedInternally, diff) +} + +func TestExecuteStorageLoop(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + checksum := createHackatomContract(t, cache) + + maxGas := TESTING_GAS_LIMIT + gasMeter1 := NewMockGasMeter(maxGas) + igasMeter1 := types.GasMeter(gasMeter1) + // instantiate it with this store + store := NewLookup(gasMeter1) + api := NewMockAPI() + balance := types.Coins{types.NewCoin(250, "ATOM")} + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, balance) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + + msg := []byte(`{"verifier": "fred", "beneficiary": "bob"}`) + + res, _, err := Instantiate(cache, checksum, env, info, msg, &igasMeter1, store, api, &querier, maxGas, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) + + // execute a storage loop + gasMeter2 := NewMockGasMeter(maxGas) + igasMeter2 := types.GasMeter(gasMeter2) + store.SetGasMeter(gasMeter2) + info = MockInfoBin(t, "fred") + start := time.Now() + _, gasReport, err := Execute(cache, checksum, env, info, []byte(`{"storage_loop":{}}`), &igasMeter2, store, api, &querier, maxGas, TESTING_PRINT_DEBUG) + diff := time.Since(start) + require.Error(t, err) + t.Logf("StorageLoop Time (%d gas): %s\n", gasReport.UsedInternally, diff) + t.Logf("Gas used: %d\n", gasMeter2.GasConsumed()) + t.Logf("Wasm gas: %d\n", gasReport.UsedInternally) + + // the "sdk gas" * GasMultiplier + the wasm cost should equal the maxGas (or be very close) + totalCost := gasReport.UsedInternally + gasMeter2.GasConsumed() + require.Equal(t, int64(maxGas), int64(totalCost)) +} + +func TestExecuteUserErrorsInApiCalls(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + checksum := createHackatomContract(t, cache) + + maxGas := TESTING_GAS_LIMIT + gasMeter1 := NewMockGasMeter(maxGas) + igasMeter1 := types.GasMeter(gasMeter1) + // instantiate it with this store + store := NewLookup(gasMeter1) + balance := types.Coins{types.NewCoin(250, "ATOM")} + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, balance) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + + defaultApi := NewMockAPI() + msg := []byte(`{"verifier": "fred", "beneficiary": "bob"}`) + res, _, err := Instantiate(cache, checksum, env, info, msg, &igasMeter1, store, defaultApi, &querier, maxGas, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) + + gasMeter2 := NewMockGasMeter(maxGas) + igasMeter2 := types.GasMeter(gasMeter2) + store.SetGasMeter(gasMeter2) + info = MockInfoBin(t, "fred") + failingApi := NewMockFailureAPI() + res, _, err = Execute(cache, checksum, env, info, []byte(`{"user_errors_in_api_calls":{}}`), &igasMeter2, store, failingApi, &querier, maxGas, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) +} + +func TestMigrate(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + checksum := createHackatomContract(t, cache) + + gasMeter := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter := types.GasMeter(gasMeter) + // instantiate it with this store + store := NewLookup(gasMeter) + api := NewMockAPI() + balance := types.Coins{types.NewCoin(250, "ATOM")} + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, balance) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + msg := []byte(`{"verifier": "fred", "beneficiary": "bob"}`) + + res, _, err := Instantiate(cache, checksum, env, info, msg, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) + + // verifier is fred + query := []byte(`{"verifier":{}}`) + data, _, err := Query(cache, checksum, env, query, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + var qres types.QueryResponse + err = json.Unmarshal(data, &qres) + require.NoError(t, err) + require.Equal(t, "", qres.Err) + require.Equal(t, string(qres.Ok), `{"verifier":"fred"}`) + + // migrate to a new verifier - alice + // we use the same code blob as we are testing hackatom self-migration + _, _, err = Migrate(cache, checksum, env, []byte(`{"verifier":"alice"}`), &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + + // should update verifier to alice + data, _, err = Query(cache, checksum, env, query, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + var qres2 types.QueryResponse + err = json.Unmarshal(data, &qres2) + require.NoError(t, err) + require.Equal(t, "", qres2.Err) + require.Equal(t, `{"verifier":"alice"}`, string(qres2.Ok)) +} + +func TestMultipleInstances(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + checksum := createHackatomContract(t, cache) + + // instance1 controlled by fred + gasMeter1 := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter1 := types.GasMeter(gasMeter1) + store1 := NewLookup(gasMeter1) + api := NewMockAPI() + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, types.Coins{types.NewCoin(100, "ATOM")}) + env := MockEnvBin(t) + info := MockInfoBin(t, "regen") + msg := []byte(`{"verifier": "fred", "beneficiary": "bob"}`) + res, cost, err := Instantiate(cache, checksum, env, info, msg, &igasMeter1, store1, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) + // we now count wasm gas charges and db writes + assert.Equal(t, uint64(0x138559c5c), cost.UsedInternally) + + // instance2 controlled by mary + gasMeter2 := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter2 := types.GasMeter(gasMeter2) + store2 := NewLookup(gasMeter2) + info = MockInfoBin(t, "chrous") + msg = []byte(`{"verifier": "mary", "beneficiary": "sue"}`) + res, cost, err = Instantiate(cache, checksum, env, info, msg, &igasMeter2, store2, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) + assert.Equal(t, uint64(0x1399177bc), cost.UsedInternally) + + // fail to execute store1 with mary + resp := exec(t, cache, checksum, "mary", store1, api, querier, 0x1218ff5d0) + require.Equal(t, "Unauthorized", resp.Err) + + // succeed to execute store1 with fred + resp = exec(t, cache, checksum, "fred", store1, api, querier, 0x22188d470) + require.Equal(t, "", resp.Err) + require.Equal(t, 1, len(resp.Ok.Messages)) + attributes := resp.Ok.Attributes + require.Equal(t, 2, len(attributes)) + require.Equal(t, "destination", attributes[1].Key) + require.Equal(t, "bob", attributes[1].Value) + + // succeed to execute store2 with mary + resp = exec(t, cache, checksum, "mary", store2, api, querier, 0x2220900f0) + require.Equal(t, "", resp.Err) + require.Equal(t, 1, len(resp.Ok.Messages)) + attributes = resp.Ok.Attributes + require.Equal(t, 2, len(attributes)) + require.Equal(t, "destination", attributes[1].Key) + require.Equal(t, "sue", attributes[1].Value) +} + +func TestSudo(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + checksum := createHackatomContract(t, cache) + + gasMeter1 := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter1 := types.GasMeter(gasMeter1) + // instantiate it with this store + store := NewLookup(gasMeter1) + api := NewMockAPI() + balance := types.Coins{types.NewCoin(250, "ATOM")} + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, balance) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + + msg := []byte(`{"verifier": "fred", "beneficiary": "bob"}`) + res, _, err := Instantiate(cache, checksum, env, info, msg, &igasMeter1, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) + + // call sudo with same store + gasMeter2 := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter2 := types.GasMeter(gasMeter2) + store.SetGasMeter(gasMeter2) + env = MockEnvBin(t) + msg = []byte(`{"steal_funds":{"recipient":"community-pool","amount":[{"amount":"700","denom":"gold"}]}}`) + res, _, err = Sudo(cache, checksum, env, msg, &igasMeter2, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + + // make sure it blindly followed orders + var result types.ContractResult + err = json.Unmarshal(res, &result) + require.NoError(t, err) + require.Equal(t, "", result.Err) + require.Equal(t, 1, len(result.Ok.Messages)) + dispatch := result.Ok.Messages[0].Msg + require.NotNil(t, dispatch.Bank, "%#v", dispatch) + require.NotNil(t, dispatch.Bank.Send, "%#v", dispatch) + send := dispatch.Bank.Send + assert.Equal(t, "community-pool", send.ToAddress) + expectedPayout := types.Coins{types.NewCoin(700, "gold")} + assert.Equal(t, expectedPayout, send.Amount) +} + +func TestDispatchSubmessage(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + checksum := createReflectContract(t, cache) + + gasMeter1 := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter1 := types.GasMeter(gasMeter1) + // instantiate it with this store + store := NewLookup(gasMeter1) + api := NewMockAPI() + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, nil) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + + msg := []byte(`{}`) + res, _, err := Instantiate(cache, checksum, env, info, msg, &igasMeter1, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) + + // dispatch a submessage + var id uint64 = 1234 + payload := types.SubMsg{ + ID: id, + Msg: types.CosmosMsg{Bank: &types.BankMsg{Send: &types.SendMsg{ + ToAddress: "friend", + Amount: types.Coins{types.NewCoin(1, "token")}, + }}}, + ReplyOn: types.ReplyAlways, + } + payloadBin, err := json.Marshal(payload) + require.NoError(t, err) + payloadMsg := []byte(fmt.Sprintf(`{"reflect_sub_msg":{"msgs":[%s]}}`, string(payloadBin))) + + gasMeter2 := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter2 := types.GasMeter(gasMeter2) + store.SetGasMeter(gasMeter2) + env = MockEnvBin(t) + res, _, err = Execute(cache, checksum, env, info, payloadMsg, &igasMeter2, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + + // make sure it blindly followed orders + var result types.ContractResult + err = json.Unmarshal(res, &result) + require.NoError(t, err) + require.Equal(t, "", result.Err) + require.Equal(t, 1, len(result.Ok.Messages)) + dispatch := result.Ok.Messages[0] + assert.Equal(t, id, dispatch.ID) + assert.Equal(t, payload.Msg, dispatch.Msg) + assert.Nil(t, dispatch.GasLimit) + assert.Equal(t, payload.ReplyOn, dispatch.ReplyOn) +} + +func TestReplyAndQuery(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + checksum := createReflectContract(t, cache) + + gasMeter1 := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter1 := types.GasMeter(gasMeter1) + // instantiate it with this store + store := NewLookup(gasMeter1) + api := NewMockAPI() + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, nil) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + + msg := []byte(`{}`) + res, _, err := Instantiate(cache, checksum, env, info, msg, &igasMeter1, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) + + var id uint64 = 1234 + data := []byte("foobar") + events := types.Events{{ + Type: "message", + Attributes: types.EventAttributes{{ + Key: "signer", + Value: "caller-addr", + }}, + }} + reply := types.Reply{ + ID: id, + Result: types.SubMsgResult{ + Ok: &types.SubMsgResponse{ + Events: events, + Data: data, + }, + }, + } + replyBin, err := json.Marshal(reply) + require.NoError(t, err) + + gasMeter2 := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter2 := types.GasMeter(gasMeter2) + store.SetGasMeter(gasMeter2) + env = MockEnvBin(t) + res, _, err = Reply(cache, checksum, env, replyBin, &igasMeter2, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireOkResponse(t, res, 0) + + // now query the state to see if it stored the data properly + badQuery := []byte(`{"sub_msg_result":{"id":7777}}`) + res, _, err = Query(cache, checksum, env, badQuery, &igasMeter2, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + requireQueryError(t, res) + + query := []byte(`{"sub_msg_result":{"id":1234}}`) + res, _, err = Query(cache, checksum, env, query, &igasMeter2, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + qres := requireQueryOk(t, res) + + var stored types.Reply + err = json.Unmarshal(qres, &stored) + require.NoError(t, err) + assert.Equal(t, id, stored.ID) + require.NotNil(t, stored.Result.Ok) + val := stored.Result.Ok + require.Equal(t, data, val.Data) + require.Equal(t, events, val.Events) +} + +func requireOkResponse(t *testing.T, res []byte, expectedMsgs int) { + var result types.ContractResult + err := json.Unmarshal(res, &result) + require.NoError(t, err) + require.Equal(t, "", result.Err) + require.Equal(t, expectedMsgs, len(result.Ok.Messages)) +} + +func requireQueryError(t *testing.T, res []byte) { + var result types.QueryResponse + err := json.Unmarshal(res, &result) + require.NoError(t, err) + require.Empty(t, result.Ok) + require.NotEmpty(t, result.Err) +} + +func requireQueryOk(t *testing.T, res []byte) []byte { + var result types.QueryResponse + err := json.Unmarshal(res, &result) + require.NoError(t, err) + require.Empty(t, result.Err) + require.NotEmpty(t, result.Ok) + return result.Ok +} + +func createHackatomContract(t *testing.T, cache Cache) []byte { + return createContract(t, cache, "../../testdata/hackatom.wasm") +} + +func createCyberpunkContract(t *testing.T, cache Cache) []byte { + return createContract(t, cache, "../../testdata/cyberpunk.wasm") +} + +func createQueueContract(t *testing.T, cache Cache) []byte { + return createContract(t, cache, "../../testdata/queue.wasm") +} + +func createReflectContract(t *testing.T, cache Cache) []byte { + return createContract(t, cache, "../../testdata/reflect.wasm") +} + +func createFloaty2(t *testing.T, cache Cache) []byte { + return createContract(t, cache, "../../testdata/floaty_2.0.wasm") +} + +func createContract(t *testing.T, cache Cache, wasmFile string) []byte { + wasm, err := ioutil.ReadFile(wasmFile) + require.NoError(t, err) + checksum, err := StoreCode(cache, wasm) + require.NoError(t, err) + return checksum +} + +// exec runs the handle tx with the given signer +func exec(t *testing.T, cache Cache, checksum []byte, signer types.HumanAddress, store types.KVStore, api *types.GoAPI, querier Querier, gasExpected uint64) types.ContractResult { + gasMeter := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter := types.GasMeter(gasMeter) + env := MockEnvBin(t) + info := MockInfoBin(t, signer) + res, cost, err := Execute(cache, checksum, env, info, []byte(`{"release":{}}`), &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + assert.Equal(t, gasExpected, cost.UsedInternally) + + var result types.ContractResult + err = json.Unmarshal(res, &result) + require.NoError(t, err) + return result +} + +func TestQuery(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + checksum := createHackatomContract(t, cache) + + // set up contract + gasMeter1 := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter1 := types.GasMeter(gasMeter1) + store := NewLookup(gasMeter1) + api := NewMockAPI() + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, types.Coins{types.NewCoin(100, "ATOM")}) + env := MockEnvBin(t) + info := MockInfoBin(t, "creator") + msg := []byte(`{"verifier": "fred", "beneficiary": "bob"}`) + _, _, err := Instantiate(cache, checksum, env, info, msg, &igasMeter1, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + + // invalid query + gasMeter2 := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter2 := types.GasMeter(gasMeter2) + store.SetGasMeter(gasMeter2) + query := []byte(`{"Raw":{"val":"config"}}`) + data, _, err := Query(cache, checksum, env, query, &igasMeter2, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + var badResp types.QueryResponse + err = json.Unmarshal(data, &badResp) + require.NoError(t, err) + require.Contains(t, badResp.Err, "Error parsing into type hackatom::msg::QueryMsg: unknown variant `Raw`, expected one of") + + // make a valid query + gasMeter3 := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter3 := types.GasMeter(gasMeter3) + store.SetGasMeter(gasMeter3) + query = []byte(`{"verifier":{}}`) + data, _, err = Query(cache, checksum, env, query, &igasMeter3, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + var qres types.QueryResponse + err = json.Unmarshal(data, &qres) + require.NoError(t, err) + require.Equal(t, "", qres.Err) + require.Equal(t, string(qres.Ok), `{"verifier":"fred"}`) +} + +func TestHackatomQuerier(t *testing.T) { + cache, cleanup := withCache(t) + defer cleanup() + checksum := createHackatomContract(t, cache) + + // set up contract + gasMeter := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter := types.GasMeter(gasMeter) + store := NewLookup(gasMeter) + api := NewMockAPI() + initBalance := types.Coins{types.NewCoin(1234, "ATOM"), types.NewCoin(65432, "ETH")} + querier := DefaultQuerier("foobar", initBalance) + + // make a valid query to the other address + query := []byte(`{"other_balance":{"address":"foobar"}}`) + // TODO The query happens before the contract is initialized. How is this legal? + env := MockEnvBin(t) + data, _, err := Query(cache, checksum, env, query, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + var qres types.QueryResponse + err = json.Unmarshal(data, &qres) + require.NoError(t, err) + require.Equal(t, "", qres.Err) + var balances types.AllBalancesResponse + err = json.Unmarshal(qres.Ok, &balances) + require.NoError(t, err) + require.Equal(t, balances.Amount, initBalance) +} + +func TestCustomReflectQuerier(t *testing.T) { + type CapitalizedQuery struct { + Text string `json:"text"` + } + + type QueryMsg struct { + Capitalized *CapitalizedQuery `json:"capitalized,omitempty"` + // There are more queries but we don't use them yet + // https://github.com/CosmWasm/cosmwasm/blob/v0.11.0-alpha3/contracts/reflect/src/msg.rs#L18-L28 + } + + type CapitalizedResponse struct { + Text string `json:"text"` + } + + cache, cleanup := withCache(t) + defer cleanup() + checksum := createReflectContract(t, cache) + + // set up contract + gasMeter := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter := types.GasMeter(gasMeter) + store := NewLookup(gasMeter) + api := NewMockAPI() + initBalance := types.Coins{types.NewCoin(1234, "ATOM")} + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, initBalance) + // we need this to handle the custom requests from the reflect contract + innerQuerier := querier.(*MockQuerier) + innerQuerier.Custom = ReflectCustom{} + querier = Querier(innerQuerier) + + // make a valid query to the other address + queryMsg := QueryMsg{ + Capitalized: &CapitalizedQuery{ + Text: "small Frys :)", + }, + } + query, err := json.Marshal(queryMsg) + require.NoError(t, err) + env := MockEnvBin(t) + data, _, err := Query(cache, checksum, env, query, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + var qres types.QueryResponse + err = json.Unmarshal(data, &qres) + require.NoError(t, err) + require.Equal(t, "", qres.Err) + + var response CapitalizedResponse + err = json.Unmarshal(qres.Ok, &response) + require.NoError(t, err) + require.Equal(t, "SMALL FRYS :)", response.Text) +} + +// TestFloats is a port of the float_instrs_are_deterministic test in cosmwasm-vm +func TestFloats(t *testing.T) { + type Value struct { + U32 *uint32 `json:"u32,omitempty"` + U64 *uint64 `json:"u64,omitempty"` + F32 *uint32 `json:"f32,omitempty"` + F64 *uint64 `json:"f64,omitempty"` + } + + // helper to print the value in the same format as Rust's Debug trait + debugStr := func(value Value) string { + if value.U32 != nil { + return fmt.Sprintf("U32(%d)", *value.U32) + } else if value.U64 != nil { + return fmt.Sprintf("U64(%d)", *value.U64) + } else if value.F32 != nil { + return fmt.Sprintf("F32(%d)", *value.F32) + } else if value.F64 != nil { + return fmt.Sprintf("F64(%d)", *value.F64) + } else { + t.FailNow() + return "" + } + } + + cache, cleanup := withCache(t) + defer cleanup() + checksum := createFloaty2(t, cache) + + gasMeter := NewMockGasMeter(TESTING_GAS_LIMIT) + igasMeter := types.GasMeter(gasMeter) + // instantiate it with this store + store := NewLookup(gasMeter) + api := NewMockAPI() + querier := DefaultQuerier(MOCK_CONTRACT_ADDR, nil) + env := MockEnvBin(t) + + // query instructions + query := []byte(`{"instructions":{}}`) + data, _, err := Query(cache, checksum, env, query, &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + var qres types.QueryResponse + err = json.Unmarshal(data, &qres) + require.NoError(t, err) + require.Equal(t, "", qres.Err) + var instructions []string + err = json.Unmarshal(qres.Ok, &instructions) + require.NoError(t, err) + // little sanity check + require.Equal(t, 70, len(instructions)) + + hasher := sha256.New() + const RUNS_PER_INSTRUCTION = 150 + for _, instr := range instructions { + for seed := 0; seed < RUNS_PER_INSTRUCTION; seed++ { + // query some input values for the instruction + msg := fmt.Sprintf(`{"random_args_for":{"instruction":"%s","seed":%d}}`, instr, seed) + data, _, err = Query(cache, checksum, env, []byte(msg), &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + require.NoError(t, err) + err = json.Unmarshal(data, &qres) + require.NoError(t, err) + require.Equal(t, "", qres.Err) + var args []Value + err = json.Unmarshal(qres.Ok, &args) + require.NoError(t, err) + + // build the run message + argStr, err := json.Marshal(args) + require.NoError(t, err) + msg = fmt.Sprintf(`{"run":{"instruction":"%s","args":%s}}`, instr, argStr) + + // run the instruction + // this might throw a runtime error (e.g. if the instruction traps) + data, _, err = Query(cache, checksum, env, []byte(msg), &igasMeter, store, api, &querier, TESTING_GAS_LIMIT, TESTING_PRINT_DEBUG) + var result string + if err != nil { + assert.ErrorContains(t, err, "Error calling the VM: Error executing Wasm: ") + // remove the prefix to make the error message the same as in the cosmwasm-vm test + result = strings.Replace(err.Error(), "Error calling the VM: Error executing Wasm: ", "", 1) + } else { + err = json.Unmarshal(data, &qres) + require.NoError(t, err) + require.Equal(t, "", qres.Err) + var response Value + err = json.Unmarshal(qres.Ok, &response) + require.NoError(t, err) + result = debugStr(response) + } + // add the result to the hash + hasher.Write([]byte(fmt.Sprintf("%s%d%s", instr, seed, result))) + } + } + + hash := hasher.Sum(nil) + require.Equal(t, "95f70fa6451176ab04a9594417a047a1e4d8e2ff809609b8f81099496bee2393", hex.EncodeToString(hash)) +} diff --git a/x/wasm/artifacts/v152/api/libwasmvm.aarch64.so b/x/wasm/artifacts/v152/api/libwasmvm.aarch64.so new file mode 100755 index 0000000..6e94c11 Binary files /dev/null and b/x/wasm/artifacts/v152/api/libwasmvm.aarch64.so differ diff --git a/x/wasm/artifacts/v152/api/libwasmvm.dylib b/x/wasm/artifacts/v152/api/libwasmvm.dylib new file mode 100755 index 0000000..0e0cecc Binary files /dev/null and b/x/wasm/artifacts/v152/api/libwasmvm.dylib differ diff --git a/x/wasm/artifacts/v152/api/libwasmvm.x86_64.so b/x/wasm/artifacts/v152/api/libwasmvm.x86_64.so new file mode 100755 index 0000000..7a38617 Binary files /dev/null and b/x/wasm/artifacts/v152/api/libwasmvm.x86_64.so differ diff --git a/x/wasm/artifacts/v152/api/link_glibclinux_aarch64.go b/x/wasm/artifacts/v152/api/link_glibclinux_aarch64.go new file mode 100644 index 0000000..8742229 --- /dev/null +++ b/x/wasm/artifacts/v152/api/link_glibclinux_aarch64.go @@ -0,0 +1,6 @@ +//go:build linux && !muslc && arm64 && !sys_wasmvm + +package api + +// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR} -L${SRCDIR} -lwasmvm.aarch64 +import "C" diff --git a/x/wasm/artifacts/v152/api/link_glibclinux_x86_64.go b/x/wasm/artifacts/v152/api/link_glibclinux_x86_64.go new file mode 100644 index 0000000..9d87a71 --- /dev/null +++ b/x/wasm/artifacts/v152/api/link_glibclinux_x86_64.go @@ -0,0 +1,6 @@ +//go:build linux && !muslc && amd64 && !sys_wasmvm + +package api + +// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR} -L${SRCDIR} -lwasmvm.x86_64 +import "C" diff --git a/x/wasm/artifacts/v152/api/link_mac.go b/x/wasm/artifacts/v152/api/link_mac.go new file mode 100644 index 0000000..e6d841e --- /dev/null +++ b/x/wasm/artifacts/v152/api/link_mac.go @@ -0,0 +1,6 @@ +//go:build darwin && !static_wasm && !sys_wasmvm + +package api + +// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR} -L${SRCDIR} -lwasmvm +import "C" diff --git a/x/wasm/artifacts/v152/api/link_mac_static.go b/x/wasm/artifacts/v152/api/link_mac_static.go new file mode 100644 index 0000000..d9132e5 --- /dev/null +++ b/x/wasm/artifacts/v152/api/link_mac_static.go @@ -0,0 +1,6 @@ +//go:build darwin && static_wasm && !sys_wasmvm + +package api + +// #cgo LDFLAGS: -L${SRCDIR} -lwasmvmstatic_darwin +import "C" diff --git a/x/wasm/artifacts/v152/api/link_muslc.go b/x/wasm/artifacts/v152/api/link_muslc.go new file mode 100644 index 0000000..c6f22e2 --- /dev/null +++ b/x/wasm/artifacts/v152/api/link_muslc.go @@ -0,0 +1,6 @@ +//go:build linux && muslc && !sys_wasmvm + +package api + +// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR} -L${SRCDIR} -lwasmvm_muslc +import "C" diff --git a/x/wasm/artifacts/v152/api/link_system.go b/x/wasm/artifacts/v152/api/link_system.go new file mode 100644 index 0000000..ad354ba --- /dev/null +++ b/x/wasm/artifacts/v152/api/link_system.go @@ -0,0 +1,6 @@ +//go:build sys_wasmvm + +package api + +// #cgo LDFLAGS: -lwasmvm +import "C" diff --git a/x/wasm/artifacts/v152/api/link_windows.go b/x/wasm/artifacts/v152/api/link_windows.go new file mode 100644 index 0000000..8e45cf0 --- /dev/null +++ b/x/wasm/artifacts/v152/api/link_windows.go @@ -0,0 +1,6 @@ +//go:build windows && !sys_wasmvm + +package api + +// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR} -L${SRCDIR} -lwasmvm +import "C" diff --git a/x/wasm/artifacts/v152/api/memory.go b/x/wasm/artifacts/v152/api/memory.go new file mode 100644 index 0000000..889f721 --- /dev/null +++ b/x/wasm/artifacts/v152/api/memory.go @@ -0,0 +1,91 @@ +package api + +/* +#include "bindings.h" +*/ +import "C" + +import "unsafe" + +// makeView creates a view into the given byte slice what allows Rust code to read it. +// The byte slice is managed by Go and will be garbage collected. Use runtime.KeepAlive +// to ensure the byte slice lives long enough. +func makeView(s []byte) C.ByteSliceView { + if s == nil { + return C.ByteSliceView{is_nil: true, ptr: cu8_ptr(nil), len: cusize(0)} + } + + // In Go, accessing the 0-th element of an empty array triggers a panic. That is why in the case + // of an empty `[]byte` we can't get the internal heap pointer to the underlying array as we do + // below with `&data[0]`. https://play.golang.org/p/xvDY3g9OqUk + if len(s) == 0 { + return C.ByteSliceView{is_nil: false, ptr: cu8_ptr(nil), len: cusize(0)} + } + + return C.ByteSliceView{ + is_nil: false, + ptr: cu8_ptr(unsafe.Pointer(&s[0])), + len: cusize(len(s)), + } +} + +// Creates a C.UnmanagedVector, which cannot be done in test files directly +func constructUnmanagedVector(is_none cbool, ptr cu8_ptr, len cusize, cap cusize) C.UnmanagedVector { + return C.UnmanagedVector{ + is_none: is_none, + ptr: ptr, + len: len, + cap: cap, + } +} + +// uninitializedUnmanagedVector returns an invalid C.UnmanagedVector +// instance. Only use then after someone wrote an instance to it. +func uninitializedUnmanagedVector() C.UnmanagedVector { + return C.UnmanagedVector{} +} + +func newUnmanagedVector(data []byte) C.UnmanagedVector { + if data == nil { + return C.new_unmanaged_vector(cbool(true), cu8_ptr(nil), cusize(0)) + } else if len(data) == 0 { + // in Go, accessing the 0-th element of an empty array triggers a panic. That is why in the case + // of an empty `[]byte` we can't get the internal heap pointer to the underlying array as we do + // below with `&data[0]`. + // https://play.golang.org/p/xvDY3g9OqUk + return C.new_unmanaged_vector(cbool(false), cu8_ptr(nil), cusize(0)) + } else { + // This will allocate a proper vector with content and return a description of it + return C.new_unmanaged_vector(cbool(false), cu8_ptr(unsafe.Pointer(&data[0])), cusize(len(data))) + } +} + +func copyAndDestroyUnmanagedVector(v C.UnmanagedVector) []byte { + var out []byte + if v.is_none { + out = nil + } else if v.cap == cusize(0) { + // There is no allocation we can copy + out = []byte{} + } else { + // C.GoBytes create a copy (https://stackoverflow.com/a/40950744/2013738) + out = C.GoBytes(unsafe.Pointer(v.ptr), cint(v.len)) + } + C.destroy_unmanaged_vector(v) + return out +} + +// copyU8Slice copies the contents of an Option<&[u8]> that was allocated on the Rust side. +// Returns nil if and only if the source is None. +func copyU8Slice(view C.U8SliceView) []byte { + if view.is_none { + return nil + } + if view.len == 0 { + // In this case, we don't want to look into the ptr + return []byte{} + } + // C.GoBytes create a copy (https://stackoverflow.com/a/40950744/2013738) + res := C.GoBytes(unsafe.Pointer(view.ptr), cint(view.len)) + return res +} diff --git a/x/wasm/artifacts/v152/api/memory_test.go b/x/wasm/artifacts/v152/api/memory_test.go new file mode 100644 index 0000000..e1dcbfa --- /dev/null +++ b/x/wasm/artifacts/v152/api/memory_test.go @@ -0,0 +1,78 @@ +package api + +import ( + "testing" + "unsafe" + + "github.com/stretchr/testify/require" +) + +func TestMakeView(t *testing.T) { + data := []byte{0xaa, 0xbb, 0x64} + dataView := makeView(data) + require.Equal(t, cbool(false), dataView.is_nil) + require.Equal(t, cusize(3), dataView.len) + + empty := []byte{} + emptyView := makeView(empty) + require.Equal(t, cbool(false), emptyView.is_nil) + require.Equal(t, cusize(0), emptyView.len) + + nilView := makeView(nil) + require.Equal(t, cbool(true), nilView.is_nil) +} + +func TestCreateAndDestroyUnmanagedVector(t *testing.T) { + // non-empty + { + original := []byte{0xaa, 0xbb, 0x64} + unmanaged := newUnmanagedVector(original) + require.Equal(t, cbool(false), unmanaged.is_none) + require.Equal(t, 3, int(unmanaged.len)) + require.GreaterOrEqual(t, 3, int(unmanaged.cap)) // Rust implementation decides this + copy := copyAndDestroyUnmanagedVector(unmanaged) + require.Equal(t, original, copy) + } + + // empty + { + original := []byte{} + unmanaged := newUnmanagedVector(original) + require.Equal(t, cbool(false), unmanaged.is_none) + require.Equal(t, 0, int(unmanaged.len)) + require.GreaterOrEqual(t, 0, int(unmanaged.cap)) // Rust implementation decides this + copy := copyAndDestroyUnmanagedVector(unmanaged) + require.Equal(t, original, copy) + } + + // none + { + var original []byte + unmanaged := newUnmanagedVector(original) + require.Equal(t, cbool(true), unmanaged.is_none) + // We must not make assumtions on the other fields in this case + copy := copyAndDestroyUnmanagedVector(unmanaged) + require.Nil(t, copy) + } +} + +// Like the test above but without `newUnmanagedVector` calls. +// Since only Rust can actually create them, we only test edge cases here. +// +//go:nocheckptr +func TestCopyDestroyUnmanagedVector(t *testing.T) { + { + // ptr, cap and len broken. Do not access those values when is_none is true + invalid_ptr := unsafe.Pointer(uintptr(42)) + uv := constructUnmanagedVector(cbool(true), cu8_ptr(invalid_ptr), cusize(0xBB), cusize(0xAA)) + copy := copyAndDestroyUnmanagedVector(uv) + require.Nil(t, copy) + } + { + // Capacity is 0, so no allocation happened. Do not access the pointer. + invalid_ptr := unsafe.Pointer(uintptr(42)) + uv := constructUnmanagedVector(cbool(false), cu8_ptr(invalid_ptr), cusize(0), cusize(0)) + copy := copyAndDestroyUnmanagedVector(uv) + require.Equal(t, []byte{}, copy) + } +} diff --git a/x/wasm/artifacts/v152/api/mock_failure.go b/x/wasm/artifacts/v152/api/mock_failure.go new file mode 100644 index 0000000..bd29f11 --- /dev/null +++ b/x/wasm/artifacts/v152/api/mock_failure.go @@ -0,0 +1,24 @@ +package api + +import ( + "fmt" + + "github.com/CosmWasm/wasmvm/types" +) + +/***** Mock types.GoAPI ****/ + +func MockFailureCanonicalAddress(human string) ([]byte, uint64, error) { + return nil, 0, fmt.Errorf("mock failure - canonical_address") +} + +func MockFailureHumanAddress(canon []byte) (string, uint64, error) { + return "", 0, fmt.Errorf("mock failure - human_address") +} + +func NewMockFailureAPI() *types.GoAPI { + return &types.GoAPI{ + HumanAddress: MockFailureHumanAddress, + CanonicalAddress: MockFailureCanonicalAddress, + } +} diff --git a/x/wasm/artifacts/v152/api/mocks.go b/x/wasm/artifacts/v152/api/mocks.go new file mode 100644 index 0000000..17618ed --- /dev/null +++ b/x/wasm/artifacts/v152/api/mocks.go @@ -0,0 +1,634 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "math" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/CosmWasm/wasmd/x/wasm/artifacts/v152/api/testdb" + "github.com/CosmWasm/wasmvm/types" +) + +/** helper constructors **/ + +const MOCK_CONTRACT_ADDR = "contract" + +func MockEnv() types.Env { + return types.Env{ + Block: types.BlockInfo{ + Height: 123, + Time: 1578939743_987654321, + ChainID: "foobar", + }, + Transaction: &types.TransactionInfo{ + Index: 4, + }, + Contract: types.ContractInfo{ + Address: MOCK_CONTRACT_ADDR, + }, + } +} + +func MockEnvBin(t *testing.T) []byte { + bin, err := json.Marshal(MockEnv()) + require.NoError(t, err) + return bin +} + +func MockInfo(sender types.HumanAddress, funds []types.Coin) types.MessageInfo { + return types.MessageInfo{ + Sender: sender, + Funds: funds, + } +} + +func MockInfoWithFunds(sender types.HumanAddress) types.MessageInfo { + return MockInfo(sender, []types.Coin{{ + Denom: "ATOM", + Amount: "100", + }}) +} + +func MockInfoBin(t *testing.T, sender types.HumanAddress) []byte { + bin, err := json.Marshal(MockInfoWithFunds(sender)) + require.NoError(t, err) + return bin +} + +func MockIBCChannel(channelID string, ordering types.IBCOrder, ibcVersion string) types.IBCChannel { + return types.IBCChannel{ + Endpoint: types.IBCEndpoint{ + PortID: "my_port", + ChannelID: channelID, + }, + CounterpartyEndpoint: types.IBCEndpoint{ + PortID: "their_port", + ChannelID: "channel-7", + }, + Order: ordering, + Version: ibcVersion, + ConnectionID: "connection-3", + } +} + +func MockIBCChannelOpenInit(channelID string, ordering types.IBCOrder, ibcVersion string) types.IBCChannelOpenMsg { + return types.IBCChannelOpenMsg{ + OpenInit: &types.IBCOpenInit{ + Channel: MockIBCChannel(channelID, ordering, ibcVersion), + }, + OpenTry: nil, + } +} + +func MockIBCChannelOpenTry(channelID string, ordering types.IBCOrder, ibcVersion string) types.IBCChannelOpenMsg { + return types.IBCChannelOpenMsg{ + OpenInit: nil, + OpenTry: &types.IBCOpenTry{ + Channel: MockIBCChannel(channelID, ordering, ibcVersion), + CounterpartyVersion: ibcVersion, + }, + } +} + +func MockIBCChannelConnectAck(channelID string, ordering types.IBCOrder, ibcVersion string) types.IBCChannelConnectMsg { + return types.IBCChannelConnectMsg{ + OpenAck: &types.IBCOpenAck{ + Channel: MockIBCChannel(channelID, ordering, ibcVersion), + CounterpartyVersion: ibcVersion, + }, + OpenConfirm: nil, + } +} + +func MockIBCChannelConnectConfirm(channelID string, ordering types.IBCOrder, ibcVersion string) types.IBCChannelConnectMsg { + return types.IBCChannelConnectMsg{ + OpenAck: nil, + OpenConfirm: &types.IBCOpenConfirm{ + Channel: MockIBCChannel(channelID, ordering, ibcVersion), + }, + } +} + +func MockIBCChannelCloseInit(channelID string, ordering types.IBCOrder, ibcVersion string) types.IBCChannelCloseMsg { + return types.IBCChannelCloseMsg{ + CloseInit: &types.IBCCloseInit{ + Channel: MockIBCChannel(channelID, ordering, ibcVersion), + }, + CloseConfirm: nil, + } +} + +func MockIBCChannelCloseConfirm(channelID string, ordering types.IBCOrder, ibcVersion string) types.IBCChannelCloseMsg { + return types.IBCChannelCloseMsg{ + CloseInit: nil, + CloseConfirm: &types.IBCCloseConfirm{ + Channel: MockIBCChannel(channelID, ordering, ibcVersion), + }, + } +} + +func MockIBCPacket(myChannel string, data []byte) types.IBCPacket { + return types.IBCPacket{ + Data: data, + Src: types.IBCEndpoint{ + PortID: "their_port", + ChannelID: "channel-7", + }, + Dest: types.IBCEndpoint{ + PortID: "my_port", + ChannelID: myChannel, + }, + Sequence: 15, + Timeout: types.IBCTimeout{ + Block: &types.IBCTimeoutBlock{ + Revision: 1, + Height: 123456, + }, + }, + } +} + +func MockIBCPacketReceive(myChannel string, data []byte) types.IBCPacketReceiveMsg { + return types.IBCPacketReceiveMsg{ + Packet: MockIBCPacket(myChannel, data), + } +} + +func MockIBCPacketAck(myChannel string, data []byte, ack types.IBCAcknowledgement) types.IBCPacketAckMsg { + packet := MockIBCPacket(myChannel, data) + + return types.IBCPacketAckMsg{ + Acknowledgement: ack, + OriginalPacket: packet, + } +} + +func MockIBCPacketTimeout(myChannel string, data []byte) types.IBCPacketTimeoutMsg { + packet := MockIBCPacket(myChannel, data) + + return types.IBCPacketTimeoutMsg{ + Packet: packet, + } +} + +/*** Mock GasMeter ****/ +// This code is borrowed from Cosmos-SDK store/types/gas.go + +// ErrorOutOfGas defines an error thrown when an action results in out of gas. +type ErrorOutOfGas struct { + Descriptor string +} + +// ErrorGasOverflow defines an error thrown when an action results gas consumption +// unsigned integer overflow. +type ErrorGasOverflow struct { + Descriptor string +} + +type MockGasMeter interface { + types.GasMeter + ConsumeGas(amount types.Gas, descriptor string) +} + +type mockGasMeter struct { + limit types.Gas + consumed types.Gas +} + +// NewMockGasMeter returns a reference to a new mockGasMeter. +func NewMockGasMeter(limit types.Gas) MockGasMeter { + return &mockGasMeter{ + limit: limit, + consumed: 0, + } +} + +func (g *mockGasMeter) GasConsumed() types.Gas { + return g.consumed +} + +func (g *mockGasMeter) Limit() types.Gas { + return g.limit +} + +// addUint64Overflow performs the addition operation on two uint64 integers and +// returns a boolean on whether or not the result overflows. +func addUint64Overflow(a, b uint64) (uint64, bool) { + if math.MaxUint64-a < b { + return 0, true + } + + return a + b, false +} + +func (g *mockGasMeter) ConsumeGas(amount types.Gas, descriptor string) { + var overflow bool + // TODO: Should we set the consumed field after overflow checking? + g.consumed, overflow = addUint64Overflow(g.consumed, amount) + if overflow { + panic(ErrorGasOverflow{descriptor}) + } + + if g.consumed > g.limit { + panic(ErrorOutOfGas{descriptor}) + } +} + +/*** Mock types.KVStore ****/ +// Much of this code is borrowed from Cosmos-SDK store/transient.go + +// Note: these gas prices are all in *wasmer gas* and (sdk gas * 100) +// +// We making simple values and non-clear multiples so it is easy to see their impact in test output +// Also note we do not charge for each read on an iterator (out of simplicity and not needed for tests) +const ( + GetPrice uint64 = 99000 + SetPrice uint64 = 187000 + RemovePrice uint64 = 142000 + RangePrice uint64 = 261000 +) + +type Lookup struct { + db *testdb.MemDB + meter MockGasMeter +} + +func NewLookup(meter MockGasMeter) *Lookup { + return &Lookup{ + db: testdb.NewMemDB(), + meter: meter, + } +} + +func (l *Lookup) SetGasMeter(meter MockGasMeter) { + l.meter = meter +} + +func (l *Lookup) WithGasMeter(meter MockGasMeter) *Lookup { + return &Lookup{ + db: l.db, + meter: meter, + } +} + +// Get wraps the underlying DB's Get method panicing on error. +func (l Lookup) Get(key []byte) []byte { + l.meter.ConsumeGas(GetPrice, "get") + v, err := l.db.Get(key) + if err != nil { + panic(err) + } + + return v +} + +// Set wraps the underlying DB's Set method panicing on error. +func (l Lookup) Set(key, value []byte) { + l.meter.ConsumeGas(SetPrice, "set") + if err := l.db.Set(key, value); err != nil { + panic(err) + } +} + +// Delete wraps the underlying DB's Delete method panicing on error. +func (l Lookup) Delete(key []byte) { + l.meter.ConsumeGas(RemovePrice, "remove") + if err := l.db.Delete(key); err != nil { + panic(err) + } +} + +// Iterator wraps the underlying DB's Iterator method panicing on error. +func (l Lookup) Iterator(start, end []byte) types.Iterator { + l.meter.ConsumeGas(RangePrice, "range") + iter, err := l.db.Iterator(start, end) + if err != nil { + panic(err) + } + + return iter +} + +// ReverseIterator wraps the underlying DB's ReverseIterator method panicing on error. +func (l Lookup) ReverseIterator(start, end []byte) types.Iterator { + l.meter.ConsumeGas(RangePrice, "range") + iter, err := l.db.ReverseIterator(start, end) + if err != nil { + panic(err) + } + + return iter +} + +var _ types.KVStore = (*Lookup)(nil) + +/***** Mock types.GoAPI ****/ + +const CanonicalLength = 32 + +const ( + CostCanonical uint64 = 440 + CostHuman uint64 = 550 +) + +func MockCanonicalAddress(human string) ([]byte, uint64, error) { + if len(human) > CanonicalLength { + return nil, 0, fmt.Errorf("human encoding too long") + } + res := make([]byte, CanonicalLength) + copy(res, []byte(human)) + return res, CostCanonical, nil +} + +func MockHumanAddress(canon []byte) (string, uint64, error) { + if len(canon) != CanonicalLength { + return "", 0, fmt.Errorf("wrong canonical length") + } + cut := CanonicalLength + for i, v := range canon { + if v == 0 { + cut = i + break + } + } + human := string(canon[:cut]) + return human, CostHuman, nil +} + +func NewMockAPI() *types.GoAPI { + return &types.GoAPI{ + HumanAddress: MockHumanAddress, + CanonicalAddress: MockCanonicalAddress, + } +} + +func TestMockApi(t *testing.T) { + human := "foobar" + canon, cost, err := MockCanonicalAddress(human) + require.NoError(t, err) + assert.Equal(t, CanonicalLength, len(canon)) + assert.Equal(t, CostCanonical, cost) + + recover, cost, err := MockHumanAddress(canon) + require.NoError(t, err) + assert.Equal(t, recover, human) + assert.Equal(t, CostHuman, cost) +} + +/**** MockQuerier ****/ + +const DEFAULT_QUERIER_GAS_LIMIT = 1_000_000 + +type MockQuerier struct { + Bank BankQuerier + Custom CustomQuerier + usedGas uint64 +} + +var _ types.Querier = &MockQuerier{} + +func DefaultQuerier(contractAddr string, coins types.Coins) types.Querier { + balances := map[string]types.Coins{ + contractAddr: coins, + } + return &MockQuerier{ + Bank: NewBankQuerier(balances), + Custom: NoCustom{}, + usedGas: 0, + } +} + +func (q *MockQuerier) Query(request types.QueryRequest, _gasLimit uint64) ([]byte, error) { + marshaled, err := json.Marshal(request) + if err != nil { + return nil, err + } + q.usedGas += uint64(len(marshaled)) + if request.Bank != nil { + return q.Bank.Query(request.Bank) + } + if request.Custom != nil { + return q.Custom.Query(request.Custom) + } + if request.Staking != nil { + return nil, types.UnsupportedRequest{Kind: "staking"} + } + if request.Wasm != nil { + return nil, types.UnsupportedRequest{Kind: "wasm"} + } + return nil, types.Unknown{} +} + +func (q MockQuerier) GasConsumed() uint64 { + return q.usedGas +} + +type BankQuerier struct { + Balances map[string]types.Coins +} + +func NewBankQuerier(balances map[string]types.Coins) BankQuerier { + bal := make(map[string]types.Coins, len(balances)) + for k, v := range balances { + dst := make([]types.Coin, len(v)) + copy(dst, v) + bal[k] = dst + } + return BankQuerier{ + Balances: bal, + } +} + +func (q BankQuerier) Query(request *types.BankQuery) ([]byte, error) { + if request.Balance != nil { + denom := request.Balance.Denom + coin := types.NewCoin(0, denom) + for _, c := range q.Balances[request.Balance.Address] { + if c.Denom == denom { + coin = c + } + } + resp := types.BalanceResponse{ + Amount: coin, + } + return json.Marshal(resp) + } + if request.AllBalances != nil { + coins := q.Balances[request.AllBalances.Address] + resp := types.AllBalancesResponse{ + Amount: coins, + } + return json.Marshal(resp) + } + return nil, types.UnsupportedRequest{Kind: "Empty BankQuery"} +} + +type CustomQuerier interface { + Query(request json.RawMessage) ([]byte, error) +} + +type NoCustom struct{} + +var _ CustomQuerier = NoCustom{} + +func (q NoCustom) Query(request json.RawMessage) ([]byte, error) { + return nil, types.UnsupportedRequest{Kind: "custom"} +} + +// ReflectCustom fulfills the requirements for testing `reflect` contract +type ReflectCustom struct{} + +var _ CustomQuerier = ReflectCustom{} + +type CustomQuery struct { + Ping *struct{} `json:"ping,omitempty"` + Capitalized *CapitalizedQuery `json:"capitalized,omitempty"` +} + +type CapitalizedQuery struct { + Text string `json:"text"` +} + +// CustomResponse is the response for all `CustomQuery`s +type CustomResponse struct { + Msg string `json:"msg"` +} + +func (q ReflectCustom) Query(request json.RawMessage) ([]byte, error) { + var query CustomQuery + err := json.Unmarshal(request, &query) + if err != nil { + return nil, err + } + var resp CustomResponse + if query.Ping != nil { + resp.Msg = "PONG" + } else if query.Capitalized != nil { + resp.Msg = strings.ToUpper(query.Capitalized.Text) + } else { + return nil, errors.New("Unsupported query") + } + return json.Marshal(resp) +} + +//************ test code for mocks *************************// + +func TestBankQuerierAllBalances(t *testing.T) { + addr := "foobar" + balance := types.Coins{types.NewCoin(12345678, "ATOM"), types.NewCoin(54321, "ETH")} + q := DefaultQuerier(addr, balance) + + // query existing account + req := types.QueryRequest{ + Bank: &types.BankQuery{ + AllBalances: &types.AllBalancesQuery{ + Address: addr, + }, + }, + } + res, err := q.Query(req, DEFAULT_QUERIER_GAS_LIMIT) + require.NoError(t, err) + var resp types.AllBalancesResponse + err = json.Unmarshal(res, &resp) + require.NoError(t, err) + assert.Equal(t, resp.Amount, balance) + + // query missing account + req2 := types.QueryRequest{ + Bank: &types.BankQuery{ + AllBalances: &types.AllBalancesQuery{ + Address: "someone-else", + }, + }, + } + res, err = q.Query(req2, DEFAULT_QUERIER_GAS_LIMIT) + require.NoError(t, err) + var resp2 types.AllBalancesResponse + err = json.Unmarshal(res, &resp2) + require.NoError(t, err) + assert.Nil(t, resp2.Amount) +} + +func TestBankQuerierBalance(t *testing.T) { + addr := "foobar" + balance := types.Coins{types.NewCoin(12345678, "ATOM"), types.NewCoin(54321, "ETH")} + q := DefaultQuerier(addr, balance) + + // query existing account with matching denom + req := types.QueryRequest{ + Bank: &types.BankQuery{ + Balance: &types.BalanceQuery{ + Address: addr, + Denom: "ATOM", + }, + }, + } + res, err := q.Query(req, DEFAULT_QUERIER_GAS_LIMIT) + require.NoError(t, err) + var resp types.BalanceResponse + err = json.Unmarshal(res, &resp) + require.NoError(t, err) + assert.Equal(t, resp.Amount, types.NewCoin(12345678, "ATOM")) + + // query existing account with missing denom + req2 := types.QueryRequest{ + Bank: &types.BankQuery{ + Balance: &types.BalanceQuery{ + Address: addr, + Denom: "BTC", + }, + }, + } + res, err = q.Query(req2, DEFAULT_QUERIER_GAS_LIMIT) + require.NoError(t, err) + var resp2 types.BalanceResponse + err = json.Unmarshal(res, &resp2) + require.NoError(t, err) + assert.Equal(t, resp2.Amount, types.NewCoin(0, "BTC")) + + // query missing account + req3 := types.QueryRequest{ + Bank: &types.BankQuery{ + Balance: &types.BalanceQuery{ + Address: "someone-else", + Denom: "ATOM", + }, + }, + } + res, err = q.Query(req3, DEFAULT_QUERIER_GAS_LIMIT) + require.NoError(t, err) + var resp3 types.BalanceResponse + err = json.Unmarshal(res, &resp3) + require.NoError(t, err) + assert.Equal(t, resp3.Amount, types.NewCoin(0, "ATOM")) +} + +func TestReflectCustomQuerier(t *testing.T) { + q := ReflectCustom{} + + // try ping + msg, err := json.Marshal(CustomQuery{Ping: &struct{}{}}) + require.NoError(t, err) + bz, err := q.Query(msg) + require.NoError(t, err) + var resp CustomResponse + err = json.Unmarshal(bz, &resp) + require.NoError(t, err) + assert.Equal(t, resp.Msg, "PONG") + + // try capital + msg2, err := json.Marshal(CustomQuery{Capitalized: &CapitalizedQuery{Text: "small."}}) + require.NoError(t, err) + bz, err = q.Query(msg2) + require.NoError(t, err) + var resp2 CustomResponse + err = json.Unmarshal(bz, &resp2) + require.NoError(t, err) + assert.Equal(t, resp2.Msg, "SMALL.") +} diff --git a/x/wasm/artifacts/v152/api/testdb/README.md b/x/wasm/artifacts/v152/api/testdb/README.md new file mode 100644 index 0000000..f7a54b4 --- /dev/null +++ b/x/wasm/artifacts/v152/api/testdb/README.md @@ -0,0 +1,5 @@ +# Testdb +This package contains an in memory DB for testing purpose only. The original code was copied from +https://github.com/tendermint/tm-db/tree/v0.6.7 to decouple project dependencies. + +All credits and a big thank you go to the original authors! diff --git a/x/wasm/artifacts/v152/api/testdb/memdb.go b/x/wasm/artifacts/v152/api/testdb/memdb.go new file mode 100644 index 0000000..5e667ce --- /dev/null +++ b/x/wasm/artifacts/v152/api/testdb/memdb.go @@ -0,0 +1,195 @@ +package testdb + +import ( + "bytes" + "fmt" + "sync" + + "github.com/google/btree" +) + +const ( + // The approximate number of items and children per B-tree node. Tuned with benchmarks. + bTreeDegree = 32 +) + +// item is a btree.Item with byte slices as keys and values +type item struct { + key []byte + value []byte +} + +// Less implements btree.Item. +func (i *item) Less(other btree.Item) bool { + // this considers nil == []byte{}, but that's ok since we handle nil endpoints + // in iterators specially anyway + return bytes.Compare(i.key, other.(*item).key) == -1 +} + +// newKey creates a new key item. +func newKey(key []byte) *item { + return &item{key: key} +} + +// newPair creates a new pair item. +func newPair(key, value []byte) *item { + return &item{key: key, value: value} +} + +// MemDB is an in-memory database backend using a B-tree for storage. +// +// For performance reasons, all given and returned keys and values are pointers to the in-memory +// database, so modifying them will cause the stored values to be modified as well. All DB methods +// already specify that keys and values should be considered read-only, but this is especially +// important with MemDB. +type MemDB struct { + mtx sync.RWMutex + btree *btree.BTree +} + +// NewMemDB creates a new in-memory database. +func NewMemDB() *MemDB { + database := &MemDB{ + btree: btree.New(bTreeDegree), + } + return database +} + +// Get implements DB. +func (db *MemDB) Get(key []byte) ([]byte, error) { + if len(key) == 0 { + return nil, errKeyEmpty + } + db.mtx.RLock() + defer db.mtx.RUnlock() + + i := db.btree.Get(newKey(key)) + if i != nil { + return i.(*item).value, nil + } + return nil, nil +} + +// Has implements DB. +func (db *MemDB) Has(key []byte) (bool, error) { + if len(key) == 0 { + return false, errKeyEmpty + } + db.mtx.RLock() + defer db.mtx.RUnlock() + + return db.btree.Has(newKey(key)), nil +} + +// Set implements DB. +func (db *MemDB) Set(key []byte, value []byte) error { + if len(key) == 0 { + return errKeyEmpty + } + if value == nil { + return errValueNil + } + db.mtx.Lock() + defer db.mtx.Unlock() + + db.set(key, value) + return nil +} + +// set sets a value without locking the mutex. +func (db *MemDB) set(key []byte, value []byte) { + db.btree.ReplaceOrInsert(newPair(key, value)) +} + +// SetSync implements DB. +func (db *MemDB) SetSync(key []byte, value []byte) error { + return db.Set(key, value) +} + +// Delete implements DB. +func (db *MemDB) Delete(key []byte) error { + if len(key) == 0 { + return errKeyEmpty + } + db.mtx.Lock() + defer db.mtx.Unlock() + + db.delete(key) + return nil +} + +// delete deletes a key without locking the mutex. +func (db *MemDB) delete(key []byte) { + db.btree.Delete(newKey(key)) +} + +// DeleteSync implements DB. +func (db *MemDB) DeleteSync(key []byte) error { + return db.Delete(key) +} + +// Close implements DB. +func (db *MemDB) Close() error { + // Close is a noop since for an in-memory database, we don't have a destination to flush + // contents to nor do we want any data loss on invoking Close(). + // See the discussion in https://github.com/tendermint/tendermint/libs/pull/56 + return nil +} + +// Print implements DB. +func (db *MemDB) Print() error { + db.mtx.RLock() + defer db.mtx.RUnlock() + + db.btree.Ascend(func(i btree.Item) bool { + item := i.(*item) + fmt.Printf("[%X]:\t[%X]\n", item.key, item.value) + return true + }) + return nil +} + +// Stats implements DB. +func (db *MemDB) Stats() map[string]string { + db.mtx.RLock() + defer db.mtx.RUnlock() + + stats := make(map[string]string) + stats["database.type"] = "memDB" + stats["database.size"] = fmt.Sprintf("%d", db.btree.Len()) + return stats +} + +// Iterator implements DB. +// Takes out a read-lock on the database until the iterator is closed. +func (db *MemDB) Iterator(start, end []byte) (Iterator, error) { + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, errKeyEmpty + } + return newMemDBIterator(db, start, end, false), nil +} + +// ReverseIterator implements DB. +// Takes out a read-lock on the database until the iterator is closed. +func (db *MemDB) ReverseIterator(start, end []byte) (Iterator, error) { + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, errKeyEmpty + } + return newMemDBIterator(db, start, end, true), nil +} + +// IteratorNoMtx makes an iterator with no mutex. +func (db *MemDB) IteratorNoMtx(start, end []byte) (Iterator, error) { + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, errKeyEmpty + } + return newMemDBIteratorMtxChoice(db, start, end, false, false), nil +} + +// ReverseIteratorNoMtx makes an iterator with no mutex. +func (db *MemDB) ReverseIteratorNoMtx(start, end []byte) (Iterator, error) { + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, errKeyEmpty + } + return newMemDBIteratorMtxChoice(db, start, end, true, false), nil +} diff --git a/x/wasm/artifacts/v152/api/testdb/memdb_iterator.go b/x/wasm/artifacts/v152/api/testdb/memdb_iterator.go new file mode 100644 index 0000000..a65efa2 --- /dev/null +++ b/x/wasm/artifacts/v152/api/testdb/memdb_iterator.go @@ -0,0 +1,157 @@ +package testdb + +import ( + "bytes" + "context" + + "github.com/google/btree" +) + +const ( + // Size of the channel buffer between traversal goroutine and iterator. Using an unbuffered + // channel causes two context switches per item sent, while buffering allows more work per + // context switch. Tuned with benchmarks. + chBufferSize = 64 +) + +// memDBIterator is a memDB iterator. +type memDBIterator struct { + ch <-chan *item + cancel context.CancelFunc + item *item + start []byte + end []byte + useMtx bool +} + +var _ Iterator = (*memDBIterator)(nil) + +// newMemDBIterator creates a new memDBIterator. +func newMemDBIterator(db *MemDB, start []byte, end []byte, reverse bool) *memDBIterator { + return newMemDBIteratorMtxChoice(db, start, end, reverse, true) +} + +func newMemDBIteratorMtxChoice(db *MemDB, start []byte, end []byte, reverse bool, useMtx bool) *memDBIterator { + ctx, cancel := context.WithCancel(context.Background()) + ch := make(chan *item, chBufferSize) + iter := &memDBIterator{ + ch: ch, + cancel: cancel, + start: start, + end: end, + useMtx: useMtx, + } + + if useMtx { + db.mtx.RLock() + } + go func() { + if useMtx { + defer db.mtx.RUnlock() + } + // Because we use [start, end) for reverse ranges, while btree uses (start, end], we need + // the following variables to handle some reverse iteration conditions ourselves. + var ( + skipEqual []byte + abortLessThan []byte + ) + visitor := func(i btree.Item) bool { + item := i.(*item) + if skipEqual != nil && bytes.Equal(item.key, skipEqual) { + skipEqual = nil + return true + } + if abortLessThan != nil && bytes.Compare(item.key, abortLessThan) == -1 { + return false + } + select { + case <-ctx.Done(): + return false + case ch <- item: + return true + } + } + switch { + case start == nil && end == nil && !reverse: + db.btree.Ascend(visitor) + case start == nil && end == nil && reverse: + db.btree.Descend(visitor) + case end == nil && !reverse: + // must handle this specially, since nil is considered less than anything else + db.btree.AscendGreaterOrEqual(newKey(start), visitor) + case !reverse: + db.btree.AscendRange(newKey(start), newKey(end), visitor) + case end == nil: + // abort after start, since we use [start, end) while btree uses (start, end] + abortLessThan = start + db.btree.Descend(visitor) + default: + // skip end and abort after start, since we use [start, end) while btree uses (start, end] + skipEqual = end + abortLessThan = start + db.btree.DescendLessOrEqual(newKey(end), visitor) + } + close(ch) + }() + + // prime the iterator with the first value, if any + if item, ok := <-ch; ok { + iter.item = item + } + + return iter +} + +// Close implements Iterator. +func (i *memDBIterator) Close() error { + i.cancel() + for range i.ch { // drain channel + } + i.item = nil + return nil +} + +// Domain implements Iterator. +func (i *memDBIterator) Domain() ([]byte, []byte) { + return i.start, i.end +} + +// Valid implements Iterator. +func (i *memDBIterator) Valid() bool { + return i.item != nil +} + +// Next implements Iterator. +func (i *memDBIterator) Next() { + i.assertIsValid() + item, ok := <-i.ch + switch { + case ok: + i.item = item + default: + i.item = nil + } +} + +// Error implements Iterator. +func (i *memDBIterator) Error() error { + return nil // famous last words +} + +// Key implements Iterator. +func (i *memDBIterator) Key() []byte { + i.assertIsValid() + return i.item.key +} + +// Value implements Iterator. +func (i *memDBIterator) Value() []byte { + i.assertIsValid() + return i.item.value +} + +func (i *memDBIterator) assertIsValid() { + if !i.Valid() { + panic("iterator is invalid") + } +} diff --git a/x/wasm/artifacts/v152/api/testdb/types.go b/x/wasm/artifacts/v152/api/testdb/types.go new file mode 100644 index 0000000..08e48dd --- /dev/null +++ b/x/wasm/artifacts/v152/api/testdb/types.go @@ -0,0 +1,18 @@ +package testdb + +import ( + "errors" + + "github.com/CosmWasm/wasmvm/types" +) + +var ( + + // errKeyEmpty is returned when attempting to use an empty or nil key. + errKeyEmpty = errors.New("key cannot be empty") + + // errValueNil is returned when attempting to set a nil value. + errValueNil = errors.New("value cannot be nil") +) + +type Iterator = types.Iterator diff --git a/x/wasm/artifacts/v152/api/version.go b/x/wasm/artifacts/v152/api/version.go new file mode 100644 index 0000000..43a13f0 --- /dev/null +++ b/x/wasm/artifacts/v152/api/version.go @@ -0,0 +1,17 @@ +package api + +/* +#include "bindings.h" +*/ +import "C" + +func LibwasmvmVersion() (string, error) { + version_ptr, err := C.version_str() + if err != nil { + return "", err + } + // For C.GoString documentation see https://pkg.go.dev/cmd/cgo and + // https://gist.github.com/helinwang/2c7bd2867ea5110f70e6431a7c80cd9b + version_copy := C.GoString(version_ptr) + return version_copy, nil +} diff --git a/x/wasm/artifacts/v152/api/version_test.go b/x/wasm/artifacts/v152/api/version_test.go new file mode 100644 index 0000000..038b1de --- /dev/null +++ b/x/wasm/artifacts/v152/api/version_test.go @@ -0,0 +1,14 @@ +package api + +import ( + "regexp" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestLibwasmvmVersion(t *testing.T) { + version, err := LibwasmvmVersion() + require.NoError(t, err) + require.Regexp(t, regexp.MustCompile(`^([0-9]+)\.([0-9]+)\.([0-9]+)(-[a-z0-9.]+)?$`), version) +} diff --git a/x/wasm/artifacts/v152/lib.go b/x/wasm/artifacts/v152/lib.go new file mode 100644 index 0000000..579af35 --- /dev/null +++ b/x/wasm/artifacts/v152/lib.go @@ -0,0 +1,581 @@ +//go:build cgo + +// This file contains the part of the API that is exposed when cgo is enabled. + +package v152 + +import ( + "encoding/json" + "fmt" + + wasmvm "github.com/CosmWasm/wasmvm" + + "github.com/CosmWasm/wasmd/x/wasm/artifacts/v152/api" + "github.com/CosmWasm/wasmvm/types" +) + +// VM is the main entry point to this library. +// You should create an instance with its own subdirectory to manage state inside, +// and call it for all cosmwasm code related actions. +type VM struct { + cache api.Cache + printDebug bool +} + +// NewVM creates a new VM. +// +// `dataDir` is a base directory for Wasm blobs and various caches. +// `supportedCapabilities` is a comma separated list of capabilities suppored by the chain. +// `memoryLimit` is the memory limit of each contract execution (in MiB) +// `printDebug` is a flag to enable/disable printing debug logs from the contract to STDOUT. This should be false in production environments. +// `cacheSize` sets the size in MiB of an in-memory cache for e.g. module caching. Set to 0 to disable. +// `deserCost` sets the gas cost of deserializing one byte of data. +func NewVM(dataDir string, supportedCapabilities string, memoryLimit uint32, printDebug bool, cacheSize uint32) (*VM, error) { + cache, err := api.InitCache(dataDir, supportedCapabilities, cacheSize, memoryLimit) + if err != nil { + return nil, err + } + return &VM{cache: cache, printDebug: printDebug}, nil +} + +// Cleanup should be called when no longer using this to free resources on the rust-side +func (vm *VM) Cleanup() { + api.ReleaseCache(vm.cache) +} + +// Deprecated: Renamed to StoreCode +func (vm *VM) Create(code wasmvm.WasmCode) (wasmvm.Checksum, error) { + return vm.StoreCode(code) +} + +// StoreCode will compile the Wasm code, and store the resulting compiled module +// as well as the original code. Both can be referenced later via Checksum. +// This must be done one time for given code, after which it can be +// instatitated many times, and each instance called many times. +// +// For example, the code for all ERC-20 contracts should be the same. +// This function stores the code for that contract only once, but it can +// be instantiated with custom inputs in the future. +// +// TODO: return gas cost? Add gas limit??? there is no metering here... +func (vm *VM) StoreCode(code wasmvm.WasmCode) (wasmvm.Checksum, error) { + return api.StoreCode(vm.cache, code) +} + +// StoreCodeUnchecked is the same as StoreCode but skips static validation checks. +// Use this for adding code that was checked before, particularly in the case of state sync. +func (vm *VM) StoreCodeUnchecked(code wasmvm.WasmCode) (wasmvm.Checksum, error) { + return api.StoreCodeUnchecked(vm.cache, code) +} + +func (vm *VM) RemoveCode(checksum wasmvm.Checksum) error { + return api.RemoveCode(vm.cache, checksum) +} + +// GetCode will load the original Wasm code for the given checksum. +// This will only succeed if that checksum was previously returned from +// a call to StoreCode. +// +// This can be used so that the (short) checksum is stored in the iavl tree +// and the larger binary blobs (wasm and compiled modules) are all managed +// by libwasmvm/cosmwasm-vm (Rust part). +func (vm *VM) GetCode(checksum wasmvm.Checksum) (wasmvm.WasmCode, error) { + return api.GetCode(vm.cache, checksum) +} + +// Pin pins a code to an in-memory cache, such that is +// always loaded quickly when executed. +// Pin is idempotent. +func (vm *VM) Pin(checksum wasmvm.Checksum) error { + return api.Pin(vm.cache, checksum) +} + +// Unpin removes the guarantee of a contract to be pinned (see Pin). +// After calling this, the code may or may not remain in memory depending on +// the implementor's choice. +// Unpin is idempotent. +func (vm *VM) Unpin(checksum wasmvm.Checksum) error { + return api.Unpin(vm.cache, checksum) +} + +// Returns a report of static analysis of the wasm contract (uncompiled). +// This contract must have been stored in the cache previously (via Create). +// Only info currently returned is if it exposes all ibc entry points, but this may grow later +func (vm *VM) AnalyzeCode(checksum wasmvm.Checksum) (*types.AnalysisReport, error) { + return api.AnalyzeCode(vm.cache, checksum) +} + +// GetMetrics some internal metrics for monitoring purposes. +func (vm *VM) GetMetrics() (*types.Metrics, error) { + return api.GetMetrics(vm.cache) +} + +// Instantiate will create a new contract based on the given Checksum. +// We can set the initMsg (contract "genesis") here, and it then receives +// an account and address and can be invoked (Execute) many times. +// +// Storage should be set with a PrefixedKVStore that this code can safely access. +// +// Under the hood, we may recompile the wasm, use a cached native compile, or even use a cached instance +// for performance. +func (vm *VM) Instantiate( + checksum wasmvm.Checksum, + env types.Env, + info types.MessageInfo, + initMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost types.UFraction, +) (*types.Response, uint64, error) { + envBin, err := json.Marshal(env) + if err != nil { + return nil, 0, err + } + infoBin, err := json.Marshal(info) + if err != nil { + return nil, 0, err + } + data, gasReport, err := api.Instantiate(vm.cache, checksum, envBin, infoBin, initMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + if err != nil { + return nil, gasReport.UsedInternally, err + } + + var result types.ContractResult + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &result) + if err != nil { + return nil, gasReport.UsedInternally, err + } + if result.Err != "" { + return nil, gasReport.UsedInternally, fmt.Errorf("%s", result.Err) + } + return result.Ok, gasReport.UsedInternally, nil +} + +// Execute calls a given contract. Since the only difference between contracts with the same Checksum is the +// data in their local storage, and their address in the outside world, we need no ContractID here. +// (That is a detail for the external, sdk-facing, side). +// +// The caller is responsible for passing the correct `store` (which must have been initialized exactly once), +// and setting the env with relevant info on this instance (address, balance, etc) +func (vm *VM) Execute( + checksum wasmvm.Checksum, + env types.Env, + info types.MessageInfo, + executeMsg []byte, + store KVStore, + goapi GoAPI, + querier Querier, + gasMeter GasMeter, + gasLimit uint64, + deserCost types.UFraction, +) (*types.Response, uint64, error) { + envBin, err := json.Marshal(env) + if err != nil { + return nil, 0, err + } + infoBin, err := json.Marshal(info) + if err != nil { + return nil, 0, err + } + data, gasReport, err := api.Execute(vm.cache, checksum, envBin, infoBin, executeMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + if err != nil { + return nil, gasReport.UsedInternally, err + } + + var result types.ContractResult + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &result) + if err != nil { + return nil, gasReport.UsedInternally, err + } + if result.Err != "" { + return nil, gasReport.UsedInternally, fmt.Errorf("%s", result.Err) + } + return result.Ok, gasReport.UsedInternally, nil +} + +// Query allows a client to execute a contract-specific query. If the result is not empty, it should be +// valid json-encoded data to return to the client. +// The meaning of path and data can be determined by the code. Path is the suffix of the abci.QueryRequest.Path +func (vm *VM) Query( + checksum wasmvm.Checksum, + env types.Env, + queryMsg []byte, + store KVStore, + goapi GoAPI, + querier Querier, + gasMeter GasMeter, + gasLimit uint64, + deserCost types.UFraction, +) ([]byte, uint64, error) { + envBin, err := json.Marshal(env) + if err != nil { + return nil, 0, err + } + data, gasReport, err := api.Query(vm.cache, checksum, envBin, queryMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + if err != nil { + return nil, gasReport.UsedInternally, err + } + + var resp types.QueryResponse + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) + if err != nil { + return nil, gasReport.UsedInternally, err + } + if resp.Err != "" { + return nil, gasReport.UsedInternally, fmt.Errorf("%s", resp.Err) + } + return resp.Ok, gasReport.UsedInternally, nil +} + +// Migrate will migrate an existing contract to a new code binary. +// This takes storage of the data from the original contract and the Checksum of the new contract that should +// replace it. This allows it to run a migration step if needed, or return an error if unable to migrate +// the given data. +// +// MigrateMsg has some data on how to perform the migration. +func (vm *VM) Migrate( + checksum Checksum, + env types.Env, + migrateMsg []byte, + store KVStore, + goapi GoAPI, + querier Querier, + gasMeter GasMeter, + gasLimit uint64, + deserCost types.UFraction, +) (*types.Response, uint64, error) { + envBin, err := json.Marshal(env) + if err != nil { + return nil, 0, err + } + data, gasReport, err := api.Migrate(vm.cache, checksum, envBin, migrateMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + if err != nil { + return nil, gasReport.UsedInternally, err + } + + var resp types.ContractResult + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) + if err != nil { + return nil, gasReport.UsedInternally, err + } + if resp.Err != "" { + return nil, gasReport.UsedInternally, fmt.Errorf("%s", resp.Err) + } + return resp.Ok, gasReport.UsedInternally, nil +} + +// Sudo allows native Go modules to make priviledged (sudo) calls on the contract. +// The contract can expose entry points that cannot be triggered by any transaction, but only via +// native Go modules, and delegate the access control to the system. +// +// These work much like Migrate (same scenario) but allows custom apps to extend the priviledged entry points +// without forking cosmwasm-vm. +func (vm *VM) Sudo( + checksum Checksum, + env types.Env, + sudoMsg []byte, + store KVStore, + goapi GoAPI, + querier Querier, + gasMeter GasMeter, + gasLimit uint64, + deserCost types.UFraction, +) (*types.Response, uint64, error) { + envBin, err := json.Marshal(env) + if err != nil { + return nil, 0, err + } + data, gasReport, err := api.Sudo(vm.cache, checksum, envBin, sudoMsg, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + if err != nil { + return nil, gasReport.UsedInternally, err + } + + var resp types.ContractResult + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) + if err != nil { + return nil, gasReport.UsedInternally, err + } + if resp.Err != "" { + return nil, gasReport.UsedInternally, fmt.Errorf("%s", resp.Err) + } + return resp.Ok, gasReport.UsedInternally, nil +} + +// Reply allows the native Go wasm modules to make a priviledged call to return the result +// of executing a SubMsg. +// +// These work much like Sudo (same scenario) but focuses on one specific case (and one message type) +func (vm *VM) Reply( + checksum Checksum, + env types.Env, + reply types.Reply, + store KVStore, + goapi GoAPI, + querier Querier, + gasMeter GasMeter, + gasLimit uint64, + deserCost types.UFraction, +) (*types.Response, uint64, error) { + envBin, err := json.Marshal(env) + if err != nil { + return nil, 0, err + } + replyBin, err := json.Marshal(reply) + if err != nil { + return nil, 0, err + } + data, gasReport, err := api.Reply(vm.cache, checksum, envBin, replyBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + if err != nil { + return nil, gasReport.UsedInternally, err + } + + var resp types.ContractResult + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) + if err != nil { + return nil, gasReport.UsedInternally, err + } + if resp.Err != "" { + return nil, gasReport.UsedInternally, fmt.Errorf("%s", resp.Err) + } + return resp.Ok, gasReport.UsedInternally, nil +} + +// IBCChannelOpen is available on IBC-enabled contracts and is a hook to call into +// during the handshake pahse +func (vm *VM) IBCChannelOpen( + checksum Checksum, + env types.Env, + msg types.IBCChannelOpenMsg, + store KVStore, + goapi GoAPI, + querier Querier, + gasMeter GasMeter, + gasLimit uint64, + deserCost types.UFraction, +) (*types.IBC3ChannelOpenResponse, uint64, error) { + envBin, err := json.Marshal(env) + if err != nil { + return nil, 0, err + } + msgBin, err := json.Marshal(msg) + if err != nil { + return nil, 0, err + } + data, gasReport, err := api.IBCChannelOpen(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + if err != nil { + return nil, gasReport.UsedInternally, err + } + + var resp types.IBCChannelOpenResult + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) + if err != nil { + return nil, gasReport.UsedInternally, err + } + if resp.Err != "" { + return nil, gasReport.UsedInternally, fmt.Errorf("%s", resp.Err) + } + return resp.Ok, gasReport.UsedInternally, nil +} + +// IBCChannelConnect is available on IBC-enabled contracts and is a hook to call into +// during the handshake pahse +func (vm *VM) IBCChannelConnect( + checksum Checksum, + env types.Env, + msg types.IBCChannelConnectMsg, + store KVStore, + goapi GoAPI, + querier Querier, + gasMeter GasMeter, + gasLimit uint64, + deserCost types.UFraction, +) (*types.IBCBasicResponse, uint64, error) { + envBin, err := json.Marshal(env) + if err != nil { + return nil, 0, err + } + msgBin, err := json.Marshal(msg) + if err != nil { + return nil, 0, err + } + data, gasReport, err := api.IBCChannelConnect(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + if err != nil { + return nil, gasReport.UsedInternally, err + } + + var resp types.IBCBasicResult + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) + if err != nil { + return nil, gasReport.UsedInternally, err + } + if resp.Err != "" { + return nil, gasReport.UsedInternally, fmt.Errorf("%s", resp.Err) + } + return resp.Ok, gasReport.UsedInternally, nil +} + +// IBCChannelClose is available on IBC-enabled contracts and is a hook to call into +// at the end of the channel lifetime +func (vm *VM) IBCChannelClose( + checksum Checksum, + env types.Env, + msg types.IBCChannelCloseMsg, + store KVStore, + goapi GoAPI, + querier Querier, + gasMeter GasMeter, + gasLimit uint64, + deserCost types.UFraction, +) (*types.IBCBasicResponse, uint64, error) { + envBin, err := json.Marshal(env) + if err != nil { + return nil, 0, err + } + msgBin, err := json.Marshal(msg) + if err != nil { + return nil, 0, err + } + data, gasReport, err := api.IBCChannelClose(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + if err != nil { + return nil, gasReport.UsedInternally, err + } + + var resp types.IBCBasicResult + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) + if err != nil { + return nil, gasReport.UsedInternally, err + } + if resp.Err != "" { + return nil, gasReport.UsedInternally, fmt.Errorf("%s", resp.Err) + } + return resp.Ok, gasReport.UsedInternally, nil +} + +// IBCPacketReceive is available on IBC-enabled contracts and is called when an incoming +// packet is received on a channel belonging to this contract +func (vm *VM) IBCPacketReceive( + checksum Checksum, + env types.Env, + msg types.IBCPacketReceiveMsg, + store KVStore, + goapi GoAPI, + querier Querier, + gasMeter GasMeter, + gasLimit uint64, + deserCost types.UFraction, +) (*types.IBCReceiveResult, uint64, error) { + envBin, err := json.Marshal(env) + if err != nil { + return nil, 0, err + } + msgBin, err := json.Marshal(msg) + if err != nil { + return nil, 0, err + } + data, gasReport, err := api.IBCPacketReceive(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + if err != nil { + return nil, gasReport.UsedInternally, err + } + + var resp types.IBCReceiveResult + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) + if err != nil { + return nil, gasReport.UsedInternally, err + } + return &resp, gasReport.UsedInternally, nil +} + +// IBCPacketAck is available on IBC-enabled contracts and is called when an +// the response for an outgoing packet (previously sent by this contract) +// is received +func (vm *VM) IBCPacketAck( + checksum Checksum, + env types.Env, + msg types.IBCPacketAckMsg, + store KVStore, + goapi GoAPI, + querier Querier, + gasMeter GasMeter, + gasLimit uint64, + deserCost types.UFraction, +) (*types.IBCBasicResponse, uint64, error) { + envBin, err := json.Marshal(env) + if err != nil { + return nil, 0, err + } + msgBin, err := json.Marshal(msg) + if err != nil { + return nil, 0, err + } + data, gasReport, err := api.IBCPacketAck(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + if err != nil { + return nil, gasReport.UsedInternally, err + } + + var resp types.IBCBasicResult + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) + if err != nil { + return nil, gasReport.UsedInternally, err + } + if resp.Err != "" { + return nil, gasReport.UsedInternally, fmt.Errorf("%s", resp.Err) + } + return resp.Ok, gasReport.UsedInternally, nil +} + +// IBCPacketTimeout is available on IBC-enabled contracts and is called when an +// outgoing packet (previously sent by this contract) will provably never be executed. +// Usually handled like ack returning an error +func (vm *VM) IBCPacketTimeout( + checksum Checksum, + env types.Env, + msg types.IBCPacketTimeoutMsg, + store KVStore, + goapi GoAPI, + querier Querier, + gasMeter GasMeter, + gasLimit uint64, + deserCost types.UFraction, +) (*types.IBCBasicResponse, uint64, error) { + envBin, err := json.Marshal(env) + if err != nil { + return nil, 0, err + } + msgBin, err := json.Marshal(msg) + if err != nil { + return nil, 0, err + } + data, gasReport, err := api.IBCPacketTimeout(vm.cache, checksum, envBin, msgBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + if err != nil { + return nil, gasReport.UsedInternally, err + } + + var resp types.IBCBasicResult + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &resp) + if err != nil { + return nil, gasReport.UsedInternally, err + } + if resp.Err != "" { + return nil, gasReport.UsedInternally, fmt.Errorf("%s", resp.Err) + } + return resp.Ok, gasReport.UsedInternally, nil +} + +func DeserializeResponse(gasLimit uint64, deserCost types.UFraction, gasReport *types.GasReport, data []byte, response any) error { + gasForDeserialization := deserCost.Mul(uint64(len(data))).Floor() + if gasLimit < gasForDeserialization+gasReport.UsedInternally { + return fmt.Errorf("Insufficient gas left to deserialize contract execution result (%d bytes)", len(data)) + } + gasReport.UsedInternally += gasForDeserialization + gasReport.Remaining -= gasForDeserialization + + err := json.Unmarshal(data, response) + if err != nil { + return err + } + + return nil +} diff --git a/x/wasm/artifacts/v152/lib_no_cgo.go b/x/wasm/artifacts/v152/lib_no_cgo.go new file mode 100644 index 0000000..385d1d9 --- /dev/null +++ b/x/wasm/artifacts/v152/lib_no_cgo.go @@ -0,0 +1,58 @@ +// This file contains the part of the API that is exposed when cgo is disabled. + +package v152 + +import ( + "bytes" + "crypto/sha256" + "fmt" + + "github.com/CosmWasm/wasmvm/types" +) + +// Checksum represents a hash of the Wasm bytecode that serves as an ID. Must be generated from this library. +type Checksum = types.Checksum + +// WasmCode is an alias for raw bytes of the wasm compiled code +type WasmCode []byte + +// KVStore is a reference to some sub-kvstore that is valid for one instance of a code +type KVStore = types.KVStore + +// GoAPI is a reference to some "precompiles", go callbacks +type GoAPI = types.GoAPI + +// Querier lets us make read-only queries on other modules +type Querier = types.Querier + +// GasMeter is a read-only version of the sdk gas meter +type GasMeter = types.GasMeter + +// LibwasmvmVersion returns the version of the loaded library +// at runtime. This can be used for debugging to verify the loaded version +// matches the expected version. +// +// When cgo is disabled at build time, this returns an error at runtime. +func LibwasmvmVersion() (string, error) { + return libwasmvmVersionImpl() +} + +// CreateChecksum performs the hashing of Wasm bytes to obtain the CosmWasm checksum. +// +// Ony Wasm blobs are allowed as inputs and a magic byte check will be performed +// to avoid accidental misusage. +func CreateChecksum(wasm []byte) (Checksum, error) { + if len(wasm) == 0 { + return Checksum{}, fmt.Errorf("Wasm bytes nil or empty") + } + if len(wasm) < 4 { + return Checksum{}, fmt.Errorf("Wasm bytes shorter than 4 bytes") + } + // magic number for Wasm is "\0asm" + // See https://webassembly.github.io/spec/core/binary/modules.html#binary-module + if !bytes.Equal(wasm[:4], []byte("\x00\x61\x73\x6D")) { + return Checksum{}, fmt.Errorf("Wasm bytes do not not start with Wasm magic number") + } + hash := sha256.Sum256(wasm) + return Checksum(hash[:]), nil +} diff --git a/x/wasm/artifacts/v152/version_cgo.go b/x/wasm/artifacts/v152/version_cgo.go new file mode 100644 index 0000000..91c4031 --- /dev/null +++ b/x/wasm/artifacts/v152/version_cgo.go @@ -0,0 +1,11 @@ +//go:build cgo + +package v152 + +import ( + "github.com/CosmWasm/wasmd/x/wasm/artifacts/v152/api" +) + +func libwasmvmVersionImpl() (string, error) { + return api.LibwasmvmVersion() +} diff --git a/x/wasm/artifacts/v152/version_no_cgo.go b/x/wasm/artifacts/v152/version_no_cgo.go new file mode 100644 index 0000000..8d15b8f --- /dev/null +++ b/x/wasm/artifacts/v152/version_no_cgo.go @@ -0,0 +1,11 @@ +//go:build !cgo + +package v152 + +import ( + "fmt" +) + +func libwasmvmVersionImpl() (string, error) { + return "", fmt.Errorf("libwasmvm unavailable since cgo is disabled") +} diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go index d2da2ac..be8a417 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -25,6 +25,7 @@ import ( paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/tendermint/tendermint/libs/log" + v152 "github.com/CosmWasm/wasmd/x/wasm/artifacts/v152" "github.com/CosmWasm/wasmd/x/wasm/ioutils" "github.com/CosmWasm/wasmd/x/wasm/types" ) @@ -82,6 +83,7 @@ type Keeper struct { paramsKeeper types.ParamsKeeper wasmVM types.WasmerEngine rpcWasmVM types.WasmerEngine + rpcWasmVM152 types.WasmerEngine wasmVMQueryHandler WasmVMQueryHandler wasmVMResponseHandler WasmVMResponseHandler messenger Messenger @@ -123,6 +125,10 @@ func NewKeeper( if err != nil { panic(err) } + rpcWasmer152, err := v152.NewVM(filepath.Join(homeDir, "wasm"), supportedFeatures, contractMemoryLimit, wasmConfig.ContractDebugMode, wasmConfig.MemoryCacheSize) + if err != nil { + panic(err) + } // set KeyTable if it has not already been set if !paramSpace.HasKeyTable() { paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) @@ -134,6 +140,7 @@ func NewKeeper( paramsKeeper: paramsKeeper, wasmVM: NewVMWrapper(wasmer), rpcWasmVM: NewVMWrapper(rpcWasmer), + rpcWasmVM152: NewVMWrapper(rpcWasmer152), accountKeeper: accountKeeper, bank: NewBankCoinTransferrer(bankKeeper), portKeeper: portKeeper, @@ -160,6 +167,9 @@ func NewKeeper( func (k Keeper) getWasmer(ctx sdk.Context) types.WasmerEngine { if ctx.IsTracing() { + if ctx.BlockHeight() < 102491599 { + return k.rpcWasmVM152 + } return k.rpcWasmVM } return k.wasmVM