Summary
kv::get_borsh has no guard for the "key not found" case. When a key is absent, kv_get returns None, get_bytes returns an empty Vec<u8>, and borsh::from_slice(&[]) returns a BorshDecodeError. Callers cannot distinguish "key was never written" from "key exists but is corrupted data."
Context
Found as an out-of-scope observation while reviewing PR for feat/wasmtime-component-model. Pre-dates this branch.
The analogous get_json function also has this behavior (empty bytes -> JsonError), but the macro-generated state loading treats JsonError as "use Default" — which works by accident. get_borsh has no such fallback.
Impact
Capsule code using Borsh serialization for KV state cannot reliably detect first-run (key absent) vs data corruption. First-run initialization logic may fail with a misleading error.
Suggested Fix
pub fn get_borsh<T: BorshDeserialize>(key: impl AsRef<[u8]>) -> Result<T, SysError> {
let bytes = get_bytes(key)?;
if bytes.is_empty() {
return Err(SysError::ApiError("key not found".into()));
}
let parsed = borsh::from_slice(&bytes)?;
Ok(parsed)
}
Or expose a get_borsh_optional that returns Result<Option<T>, SysError>.
Summary
kv::get_borshhas no guard for the "key not found" case. When a key is absent,kv_getreturnsNone,get_bytesreturns an emptyVec<u8>, andborsh::from_slice(&[])returns aBorshDecodeError. Callers cannot distinguish "key was never written" from "key exists but is corrupted data."Context
Found as an out-of-scope observation while reviewing PR for
feat/wasmtime-component-model. Pre-dates this branch.The analogous
get_jsonfunction also has this behavior (empty bytes ->JsonError), but the macro-generated state loading treatsJsonErroras "use Default" — which works by accident.get_borshhas no such fallback.Impact
Capsule code using Borsh serialization for KV state cannot reliably detect first-run (key absent) vs data corruption. First-run initialization logic may fail with a misleading error.
Suggested Fix
Or expose a
get_borsh_optionalthat returnsResult<Option<T>, SysError>.