Skip to main content

ownable_std/abi/
mod.rs

1use crate::IdbStateDump;
2use cosmwasm_std::Response;
3use serde::{Deserialize, Serialize};
4use std::fmt::Display;
5use std::panic::UnwindSafe;
6
7pub const HOST_ABI_VERSION: &str = "1";
8pub const HOST_ABI_MANIFEST_FIELD: &str = "ownablesAbi";
9pub const HOST_ABI_WIRE_FORMAT: &str = "cbor";
10pub const HOST_ABI_WIRE_FORMAT_MANIFEST_FIELD: &str = "wireFormat";
11pub const ERR_INVALID_POINTER: &str = "INVALID_POINTER";
12pub const ERR_INVALID_CBOR: &str = "INVALID_CBOR";
13pub const ERR_SERIALIZATION_FAILED: &str = "SERIALIZATION_FAILED";
14pub const ERR_HANDLER_PANIC: &str = "HANDLER_PANIC";
15
16/// Error object returned by the host ABI envelope.
17#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
18pub struct HostAbiError {
19    pub code: Option<String>,
20    pub message: String,
21}
22
23impl HostAbiError {
24    /// Creates an error without a machine-readable code.
25    pub fn new(message: impl Into<String>) -> Self {
26        Self {
27            code: None,
28            message: message.into(),
29        }
30    }
31
32    /// Creates an error with both code and message.
33    pub fn with_code(code: impl Into<String>, message: impl Into<String>) -> Self {
34        Self {
35            code: Some(code.into()),
36            message: message.into(),
37        }
38    }
39
40    /// Creates an error from any displayable value.
41    pub fn from_display(err: impl Display) -> Self {
42        Self::new(err.to_string())
43    }
44}
45
46impl From<String> for HostAbiError {
47    fn from(value: String) -> Self {
48        Self::new(value)
49    }
50}
51
52impl From<&str> for HostAbiError {
53    fn from(value: &str) -> Self {
54        Self::new(value)
55    }
56}
57
58#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
59/// Top-level response envelope used by host ABI exports.
60pub struct HostAbiResponse {
61    pub success: bool,
62    #[serde(default, skip_serializing_if = "Vec::is_empty", with = "serde_bytes")]
63    pub payload: Vec<u8>,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub error_code: Option<String>,
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub error_message: Option<String>,
68}
69
70impl HostAbiResponse {
71    /// Builds a successful response with encoded payload bytes.
72    pub fn ok(payload: Vec<u8>) -> Self {
73        Self {
74            success: true,
75            payload,
76            error_code: None,
77            error_message: None,
78        }
79    }
80
81    /// Builds an error response from any value convertible to [`HostAbiError`].
82    pub fn err(error: impl Into<HostAbiError>) -> Self {
83        let error = error.into();
84        Self {
85            success: false,
86            payload: Vec::new(),
87            error_code: error.code,
88            error_message: Some(error.message),
89        }
90    }
91}
92
93/// Packs `(ptr, len)` into a single `u64` where the high 32 bits hold length.
94pub fn pack_ptr_len(ptr: u32, len: u32) -> u64 {
95    ((len as u64) << 32) | (ptr as u64)
96}
97
98/// Unpacks a pointer/length pair produced by [`pack_ptr_len`].
99pub fn unpack_ptr_len(packed: u64) -> (u32, u32) {
100    let ptr = packed as u32;
101    let len = (packed >> 32) as u32;
102    (ptr, len)
103}
104
105/// Allocates `len` bytes in wasm linear memory and returns pointer.
106pub fn alloc(len: u32) -> u32 {
107    if len == 0 {
108        return 0;
109    }
110
111    let mut buffer = Vec::<u8>::with_capacity(len as usize);
112    let ptr = buffer.as_mut_ptr();
113    std::mem::forget(buffer);
114    ptr as u32
115}
116
117/// Frees memory previously allocated via `alloc`.
118///
119/// # Safety
120/// The `(ptr, len)` pair must come from `alloc` and must not be freed twice.
121pub unsafe fn free(ptr: u32, len: u32) {
122    if ptr == 0 || len == 0 {
123        return;
124    }
125
126    // SAFETY: Caller guarantees the pointer/length pair originated from `alloc`.
127    unsafe {
128        drop(Vec::from_raw_parts(
129            ptr as *mut u8,
130            len as usize,
131            len as usize,
132        ));
133    }
134}
135
136/// Reads a byte slice from wasm linear memory.
137pub fn read_memory(ptr: u32, len: u32) -> Result<Vec<u8>, HostAbiError> {
138    if len == 0 {
139        return Ok(Vec::new());
140    }
141    if ptr == 0 {
142        return Err(HostAbiError::with_code(
143            ERR_INVALID_POINTER,
144            "received null pointer for non-empty input",
145        ));
146    }
147
148    // SAFETY: The host is expected to pass a valid input buffer in wasm memory.
149    let bytes = unsafe { std::slice::from_raw_parts(ptr as *const u8, len as usize) };
150    Ok(bytes.to_vec())
151}
152
153/// Writes bytes into wasm linear memory and returns a packed pointer/length pair.
154pub fn write_memory(data: &[u8]) -> u64 {
155    let len = data.len() as u32;
156    if len == 0 {
157        return pack_ptr_len(0, 0);
158    }
159
160    let ptr = alloc(len);
161    if ptr == 0 {
162        return pack_ptr_len(0, 0);
163    }
164
165    // SAFETY: `ptr` points to `len` bytes allocated via `alloc`.
166    unsafe {
167        std::ptr::copy_nonoverlapping(data.as_ptr(), ptr as *mut u8, len as usize);
168    }
169    pack_ptr_len(ptr, len)
170}
171
172/// Deserialize CBOR bytes into a value.
173pub fn cbor_from_slice<T: serde::de::DeserializeOwned>(bytes: &[u8]) -> Result<T, HostAbiError> {
174    ciborium::de::from_reader(bytes)
175        .map_err(|e| HostAbiError::with_code(ERR_INVALID_CBOR, e.to_string()))
176}
177
178/// Serialize a value to CBOR bytes.
179pub fn cbor_to_vec<T: serde::Serialize>(value: &T) -> Result<Vec<u8>, HostAbiError> {
180    let mut buf = Vec::new();
181    ciborium::ser::into_writer(value, &mut buf)
182        .map_err(|e| HostAbiError::with_code(ERR_SERIALIZATION_FAILED, e.to_string()))?;
183    Ok(buf)
184}
185
186/// Serializes [`HostAbiResponse`] to CBOR and writes it into wasm memory.
187pub fn encode_response(response: &HostAbiResponse) -> u64 {
188    let encoded = cbor_to_vec(response).unwrap_or_else(|error| {
189        let fallback = HostAbiResponse::err(HostAbiError::with_code(
190            ERR_SERIALIZATION_FAILED,
191            error.message,
192        ));
193        cbor_to_vec(&fallback).unwrap_or_default()
194    });
195    write_memory(&encoded)
196}
197
198/// Reads input bytes, invokes a handler, and returns an encoded ABI response.
199pub fn dispatch<E, F>(ptr: u32, len: u32, handler: F) -> u64
200where
201    E: Into<HostAbiError>,
202    F: FnOnce(&[u8]) -> Result<Vec<u8>, E> + UnwindSafe,
203{
204    let response = dispatch_response(read_memory(ptr, len), handler);
205    encode_response(&response)
206}
207
208/// Converts handler output (or failure) into [`HostAbiResponse`].
209pub fn dispatch_response<E, F>(input: Result<Vec<u8>, HostAbiError>, handler: F) -> HostAbiResponse
210where
211    E: Into<HostAbiError>,
212    F: FnOnce(&[u8]) -> Result<Vec<u8>, E> + UnwindSafe,
213{
214    match input {
215        Ok(input) => match std::panic::catch_unwind(|| handler(&input)) {
216            Ok(handler_result) => match handler_result {
217                Ok(payload) => HostAbiResponse::ok(payload),
218                Err(error) => HostAbiResponse::err(error.into()),
219            },
220            Err(_) => HostAbiResponse::err(HostAbiError::with_code(
221                ERR_HANDLER_PANIC,
222                "handler panicked",
223            )),
224        },
225        Err(error) => HostAbiResponse::err(error),
226    }
227}
228
229#[macro_export]
230macro_rules! ownable_host_abi_v1 {
231    (
232        instantiate = $instantiate:path,
233        execute = $execute:path,
234        query = $query:path,
235        register = $register:path,
236        ingest = $ingest:path,
237        encode_public_event = $encode_public_event:path $(,)?
238    ) => {
239        #[unsafe(no_mangle)]
240        pub extern "C" fn ownable_alloc(len: u32) -> u32 {
241            $crate::abi::alloc(len)
242        }
243
244        #[unsafe(no_mangle)]
245        pub extern "C" fn ownable_free(ptr: u32, len: u32) {
246            // SAFETY: Host must only pass pointers obtained through `ownable_alloc`.
247            unsafe { $crate::abi::free(ptr, len) }
248        }
249
250        #[unsafe(no_mangle)]
251        pub extern "C" fn ownable_instantiate(ptr: u32, len: u32) -> u64 {
252            $crate::abi::dispatch(ptr, len, $instantiate)
253        }
254
255        #[unsafe(no_mangle)]
256        pub extern "C" fn ownable_execute(ptr: u32, len: u32) -> u64 {
257            $crate::abi::dispatch(ptr, len, $execute)
258        }
259
260        #[unsafe(no_mangle)]
261        pub extern "C" fn ownable_query(ptr: u32, len: u32) -> u64 {
262            $crate::abi::dispatch(ptr, len, $query)
263        }
264
265        #[unsafe(no_mangle)]
266        pub extern "C" fn ownable_register(ptr: u32, len: u32) -> u64 {
267            $crate::abi::dispatch(ptr, len, $register)
268        }
269
270        #[unsafe(no_mangle)]
271        pub extern "C" fn ownable_ingest(ptr: u32, len: u32) -> u64 {
272            $crate::abi::dispatch(ptr, len, $ingest)
273        }
274
275        #[unsafe(no_mangle)]
276        pub extern "C" fn ownable_encode_public_event(ptr: u32, len: u32) -> u64 {
277            $crate::abi::dispatch(ptr, len, $encode_public_event)
278        }
279    };
280}
281
282/// A single key-value attribute from a contract response.
283#[derive(Serialize, Deserialize, Clone, Debug)]
284pub struct AbiAttribute {
285    pub key: String,
286    pub value: String,
287}
288
289/// An event emitted by a contract response.
290#[derive(Serialize, Deserialize, Clone, Debug)]
291pub struct AbiEvent {
292    #[serde(rename = "type")]
293    pub kind: String,
294    pub attributes: Vec<AbiAttribute>,
295}
296
297/// CBOR-native representation of a cosmwasm execute/instantiate/register/ingest Response.
298/// Only carries the fields the host actually needs; skips messages and sub-messages.
299#[derive(Serialize, Deserialize, Clone, Debug)]
300pub struct AbiResponse {
301    pub attributes: Vec<AbiAttribute>,
302    #[serde(default, skip_serializing_if = "Vec::is_empty")]
303    pub events: Vec<AbiEvent>,
304}
305
306impl From<Response> for AbiResponse {
307    fn from(r: Response) -> Self {
308        AbiResponse {
309            attributes: r
310                .attributes
311                .into_iter()
312                .map(|a| AbiAttribute {
313                    key: a.key,
314                    value: a.value,
315                })
316                .collect(),
317            events: r
318                .events
319                .into_iter()
320                .map(|e| AbiEvent {
321                    kind: e.ty,
322                    attributes: e
323                        .attributes
324                        .into_iter()
325                        .map(|a| AbiAttribute {
326                            key: a.key,
327                            value: a.value,
328                        })
329                        .collect(),
330                })
331                .collect(),
332        }
333    }
334}
335
336/// The inner payload returned by every ABI handler, CBOR-encoded inside `HostAbiResponse.payload`.
337///
338/// - `result` carries raw bytes:
339///   - for execute/instantiate/register/ingest: a CBOR-encoded `AbiResponse`
340///   - for encode_public_event: raw ABI bytes
341///   - for query: the raw bytes from cosmwasm `Binary` (JSON-encoded by `to_json_binary`)
342/// - `mem` is present for state-mutating calls; absent for queries.
343#[derive(Serialize, Deserialize, Clone, Debug)]
344pub struct AbiResultPayload {
345    #[serde(with = "serde_bytes")]
346    pub result: Vec<u8>,
347    #[serde(skip_serializing_if = "Option::is_none")]
348    pub mem: Option<IdbStateDump>,
349}
350
351#[cfg(test)]
352mod tests {
353    use super::*;
354
355    mod macro_exports {
356        use super::super::HostAbiError;
357
358        fn passthrough_handler(input: &[u8]) -> Result<Vec<u8>, HostAbiError> {
359            Ok(input.to_vec())
360        }
361
362        crate::ownable_host_abi_v1!(
363            instantiate = passthrough_handler,
364            execute = passthrough_handler,
365            query = passthrough_handler,
366            register = passthrough_handler,
367            ingest = passthrough_handler,
368            encode_public_event = passthrough_handler,
369        );
370
371        #[test]
372        fn register_export_is_generated() {
373            let export: extern "C" fn(u32, u32) -> u64 = ownable_register;
374            let _ = export;
375        }
376
377        #[test]
378        fn ingest_export_is_generated() {
379            let export: extern "C" fn(u32, u32) -> u64 = ownable_ingest;
380            let _ = export;
381        }
382
383        #[test]
384        fn encode_public_event_export_is_generated() {
385            let export: extern "C" fn(u32, u32) -> u64 = ownable_encode_public_event;
386            let _ = export;
387        }
388    }
389
390    #[test]
391    fn dispatch_response_supports_register_style_handlers() {
392        let response = dispatch_response::<HostAbiError, _>(Ok(b"register".to_vec()), |input| {
393            Ok(input.to_vec())
394        });
395        assert!(response.success);
396        assert_eq!(response.payload, b"register");
397    }
398
399    #[test]
400    fn dispatch_response_supports_ingest_style_handlers() {
401        let response = dispatch_response::<HostAbiError, _>(Ok(b"ingest".to_vec()), |input| {
402            Ok(input.to_vec())
403        });
404        assert!(response.success);
405        assert_eq!(response.payload, b"ingest");
406    }
407
408    #[test]
409    fn dispatch_response_supports_encode_public_event_style_handlers() {
410        let response =
411            dispatch_response::<HostAbiError, _>(Ok(b"encode".to_vec()), |input| Ok(input.to_vec()));
412        assert!(response.success);
413        assert_eq!(response.payload, b"encode");
414    }
415
416    #[test]
417    fn pack_unpack_round_trip() {
418        let packed = pack_ptr_len(42, 128);
419        assert_eq!(unpack_ptr_len(packed), (42, 128));
420    }
421
422    #[test]
423    fn response_serializes_error_fields() {
424        let response = HostAbiResponse::err(HostAbiError::with_code("E", "failed"));
425        let encoded = cbor_to_vec(&response).expect("serialize");
426        let decoded: HostAbiResponse = cbor_from_slice(&encoded).expect("deserialize");
427        assert!(!decoded.success);
428        assert_eq!(decoded.error_code.as_deref(), Some("E"));
429        assert_eq!(decoded.error_message.as_deref(), Some("failed"));
430    }
431
432    #[test]
433    fn response_omits_empty_payload() {
434        let response = HostAbiResponse::err("boom");
435        let encoded = cbor_to_vec(&response).expect("serialize");
436        let decoded: HostAbiResponse = cbor_from_slice(&encoded).expect("deserialize");
437        assert!(decoded.payload.is_empty());
438    }
439
440    #[test]
441    fn payload_round_trips_as_cbor_bytes_not_array() {
442        // Ensure Vec<u8> payload is encoded as CBOR byte string (major type 2),
443        // not a CBOR array of integers (major type 4). cbor-x on the JS side
444        // decodes byte strings to Uint8Array; it cannot decode a CBOR array as
445        // input to a second decode() call.
446        let inner = vec![0x01u8, 0x02, 0x03];
447        let response = HostAbiResponse::ok(inner.clone());
448        let encoded = cbor_to_vec(&response).expect("serialize");
449
450        // The "payload" value in the CBOR map must be a byte string (major type 2),
451        // NOT an array (major type 4).
452        let value: ciborium::Value =
453            ciborium::de::from_reader(&encoded[..]).expect("parse as Value");
454        if let ciborium::Value::Map(entries) = value {
455            let payload_val = entries
456                .into_iter()
457                .find(|(k, _)| k == &ciborium::Value::Text("payload".into()))
458                .map(|(_, v)| v)
459                .expect("payload key present");
460            assert!(
461                matches!(payload_val, ciborium::Value::Bytes(_)),
462                "payload must be CBOR bytes, got {:?}",
463                payload_val
464            );
465        } else {
466            panic!("expected CBOR map");
467        }
468
469        // Also verify round-trip correctness.
470        let decoded: HostAbiResponse = cbor_from_slice(&encoded).expect("deserialize");
471        assert_eq!(decoded.payload, inner);
472    }
473
474    #[test]
475    fn cbor_parse_error_maps_to_invalid_cbor_code() {
476        let result: Result<HostAbiResponse, _> = cbor_from_slice(b"\xff");
477        let abi_err = result.expect_err("should fail on invalid CBOR");
478        assert_eq!(abi_err.code.as_deref(), Some(ERR_INVALID_CBOR));
479    }
480
481    #[test]
482    fn dispatch_converts_panic_into_structured_error() {
483        let response = dispatch_response::<HostAbiError, _>(
484            Ok(Vec::new()),
485            |_| -> Result<Vec<u8>, HostAbiError> {
486                panic!("boom");
487            },
488        );
489        assert!(!response.success);
490        assert_eq!(response.error_code.as_deref(), Some(ERR_HANDLER_PANIC));
491        assert_eq!(response.error_message.as_deref(), Some("handler panicked"));
492    }
493}