diff --git a/cosmwasm/packages/sgx-vm/src/cache.rs b/cosmwasm/packages/sgx-vm/src/cache.rs index 415dabefa..cf5bba487 100644 --- a/cosmwasm/packages/sgx-vm/src/cache.rs +++ b/cosmwasm/packages/sgx-vm/src/cache.rs @@ -45,6 +45,12 @@ pub struct CosmCache, } +#[derive(PartialEq, Debug)] +pub struct AnalysisReport { + pub has_ibc_entry_points: bool, + pub required_features: HashSet, +} + impl CosmCache where S: Storage + 'static, @@ -115,6 +121,39 @@ where } } + pub fn deserialize_wasm(wasm_code: &[u8]) -> VmResult { + deserialize_buffer(wasm_code).map_err(|err| { + VmError::static_validation_err(format!( + "Wasm bytecode could not be deserialized. Deserialization error: \"{}\"", + err + )) + }) + } + + /// Returns true if and only if all IBC entry points ([`REQUIRED_IBC_EXPORTS`]) + /// exist as exported functions. This does not guarantee the entry points + /// are functional and for simplicity does not even check their signatures. + pub fn has_ibc_entry_points(module: &impl ExportInfo) -> bool { + let available_exports = module.exported_function_names(None); + REQUIRED_IBC_EXPORTS + .iter() + .all(|required| available_exports.contains(*required)) + } + + /// Performs static anlyzation on this Wasm without compiling or instantiating it. + /// + /// Once the contract was stored via [`save_wasm`], this can be called at any point in time. + /// It does not depend on any caching of the contract. + pub fn analyze(&self, checksum: &Checksum) -> VmResult { + // Here we could use a streaming deserializer to slightly improve performance. However, this way it is DRYer. + let wasm = self.load_wasm(checksum)?; + let module = deserialize_wasm(&wasm)?; + Ok(AnalysisReport { + has_ibc_entry_points: has_ibc_entry_points(&module), + required_features: required_features_from_module(&module), + }) + } + /// Returns an Instance tied to a previously saved Wasm. /// Depending on availability, this is either generated from a cached instance, a cached module or Wasm code. pub fn get_instance( diff --git a/go-cosmwasm/api/bindings.h b/go-cosmwasm/api/bindings.h index 6a4cdddcb..d8bc73509 100644 --- a/go-cosmwasm/api/bindings.h +++ b/go-cosmwasm/api/bindings.h @@ -53,6 +53,22 @@ typedef struct Buffer { uintptr_t cap; } Buffer; +/** + * The result type of the FFI function analyze_code. + * + * Please note that the unmanaged vector in `required_features` + * 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 features. + * This is never None/nil. + */ + Buffer required_features; +} AnalysisReport; + typedef struct EnclaveRuntimeConfig { uint8_t module_cache_size; } EnclaveRuntimeConfig; @@ -186,6 +202,10 @@ Buffer query(cache_t *cache, uint64_t *gas_used, Buffer *err); +AnalysisReport analyze_code(cache_t *cache, + ByteSliceView checksum, + Buffer *error_msg); + /** * frees a cache reference * diff --git a/go-cosmwasm/api/lib.go b/go-cosmwasm/api/lib.go index c0d172e3d..c8524fdc0 100644 --- a/go-cosmwasm/api/lib.go +++ b/go-cosmwasm/api/lib.go @@ -258,6 +258,25 @@ func Query( return receiveVector(res), uint64(gasUsed), nil } +func AnalyzeCode( + cache Cache, + codeHash []byte, +) (*v1types.AnalysisReport, error) { + cs := sendSlice(codeHash) + defer runtime.KeepAlive(codeHash) + errMsg := C.Buffer{} + report, err := C.analyze_code(cache.ptr, cs, &errMsg) + + if err != nil { + return nil, errorWithMessage(err, errMsg) + } + res := v1types.AnalysisReport{ + HasIBCEntryPoints: bool(report.has_ibc_entry_points), + RequiredFeatures: string(receiveVector(report.required_features)), + } + return &res, nil +} + // KeyGen Send KeyGen request to enclave func KeyGen() ([]byte, error) { errmsg := C.Buffer{} @@ -307,18 +326,3 @@ func errorWithMessage(err error, b C.Buffer) error { } return fmt.Errorf("%s", string(msg)) } - -func AnalyzeCode(cache Cache, checksum []byte) (*v1types.AnalysisReport, error) { - cs := makeView(checksum) - defer runtime.KeepAlive(checksum) - errmsg := newUnmanagedVector(nil) - report, err := C.analyze_code(cache.ptr, cs, &errmsg) - if err != nil { - return nil, errorWithMessage(err, errmsg) - } - res := types.AnalysisReport{ - HasIBCEntryPoints: bool(report.has_ibc_entry_points), - RequiredFeatures: string(copyAndDestroyUnmanagedVector(report.required_features)), - } - return &res, nil -} diff --git a/go-cosmwasm/lib.go b/go-cosmwasm/lib.go index 11693ae96..0a94288f7 100644 --- a/go-cosmwasm/lib.go +++ b/go-cosmwasm/lib.go @@ -236,41 +236,12 @@ func (w *Wasmer) Query( return resp.Ok, gasUsed, nil } -// AnalyzeCode will statically analyze the code. +// AnalyzeCode 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 // Currently just reports if it exposes all IBC entry points. func (w *Wasmer) AnalyzeCode( codeHash []byte, ) (*v1types.AnalysisReport, error) { - data, gasUsed, err := api.AnalyzeCode(codeHash) - if err != nil { - return nil, nil, gasUsed, err - } - - key := data[0:64] - data = data[64:] - - var respV010 v010types.InitResult - jsonErrV010 := json.Unmarshal(data, &respV010) - - if jsonErrV010 == nil { - // v0.10 response - if respV010.Err != nil { - return nil, nil, gasUsed, fmt.Errorf("%v", respV010.Err) - } - return respV010.Ok, key, gasUsed, nil - } - - var respV1 v1types.ContractResult - jsonErrV1 := json.Unmarshal(data, &respV1) - - if jsonErrV1 == nil { - // v1 response - if respV1.Err != "" { - return nil, nil, gasUsed, fmt.Errorf(respV1.Err) - } - return respV1.Ok, key, gasUsed, nil - } - - // unidentified response 🤷 - return nil, nil, gasUsed, fmt.Errorf("cannot detect response type, v0.10: %v or v0.16: %v", jsonErrV010, jsonErrV1) + return api.AnalyzeCode(w.cache, codeHash) } diff --git a/go-cosmwasm/src/lib.rs b/go-cosmwasm/src/lib.rs index 09dc11ac8..f14980cf1 100644 --- a/go-cosmwasm/src/lib.rs +++ b/go-cosmwasm/src/lib.rs @@ -499,6 +499,46 @@ fn do_query( Ok(res?) } +/// The result type of the FFI function analyze_code. +/// +/// Please note that the unmanaged vector in `required_features` +/// has to be destroyed exactly once. When calling `analyze_code` +/// from Go this is done via `C.destroy_unmanaged_vector`. +#[repr(C)] +#[derive(Copy, Clone, Default, Debug, PartialEq)] +pub struct AnalysisReport { + pub has_ibc_entry_points: bool, + /// An UTF-8 encoded comma separated list of reqired features. + /// This is never None/nil. + pub required_features: Buffer, +} + +#[no_mangle] +pub extern "C" fn analyze_code( + cache: *mut cache_t, + checksum: ByteSliceView, + error_msg: Option<&mut Buffer>, +) -> AnalysisReport { + let r = match to_cache(cache) { + Some(c) => catch_unwind(AssertUnwindSafe(move || do_analyze_code(c, checksum))) + .unwrap_or_else(|_| Err(Error::panic())), + None => Err(Error::unset_arg(CACHE_ARG)), + }; + handle_c_error_default(r, error_msg) +} + +fn do_analyze_code( + cache: &mut Cache, + checksum: ByteSliceView, +) -> Result { + let checksum: Checksum = checksum + .read() + .ok_or_else(|| Error::unset_arg(CHECKSUM_ARG))? + .try_into()?; + let report = cache.analyze(&checksum)?; + Ok(report.into()) +} + #[no_mangle] pub extern "C" fn key_gen(err: Option<&mut Buffer>) -> Buffer { match untrusted_key_gen() {