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#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
18pub struct HostAbiError {
19 pub code: Option<String>,
20 pub message: String,
21}
22
23impl HostAbiError {
24 pub fn new(message: impl Into<String>) -> Self {
26 Self {
27 code: None,
28 message: message.into(),
29 }
30 }
31
32 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 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)]
59pub 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 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 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
93pub fn pack_ptr_len(ptr: u32, len: u32) -> u64 {
95 ((len as u64) << 32) | (ptr as u64)
96}
97
98pub 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
105pub 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
117pub unsafe fn free(ptr: u32, len: u32) {
122 if ptr == 0 || len == 0 {
123 return;
124 }
125
126 unsafe {
128 drop(Vec::from_raw_parts(
129 ptr as *mut u8,
130 len as usize,
131 len as usize,
132 ));
133 }
134}
135
136pub 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 let bytes = unsafe { std::slice::from_raw_parts(ptr as *const u8, len as usize) };
150 Ok(bytes.to_vec())
151}
152
153pub 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 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
172pub 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
178pub 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
186pub 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
198pub 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
208pub 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 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#[derive(Serialize, Deserialize, Clone, Debug)]
284pub struct AbiAttribute {
285 pub key: String,
286 pub value: String,
287}
288
289#[derive(Serialize, Deserialize, Clone, Debug)]
291pub struct AbiEvent {
292 #[serde(rename = "type")]
293 pub kind: String,
294 pub attributes: Vec<AbiAttribute>,
295}
296
297#[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#[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 let inner = vec![0x01u8, 0x02, 0x03];
447 let response = HostAbiResponse::ok(inner.clone());
448 let encoded = cbor_to_vec(&response).expect("serialize");
449
450 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 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}