Skip to main content

truthlinked_sdk/
env.rs

1//! Low-level WASM host environment bindings.
2//!
3//! This module provides direct access to the TruthLinked runtime host functions
4//! via WASM imports. These are the raw building blocks used by higher-level SDK APIs.
5//!
6//! Most contract developers should use the higher-level APIs in other modules
7//! rather than calling these functions directly.
8
9use crate::error::{Error, Result};
10
11/// Size of storage keys in bytes (32 bytes / 256 bits).
12pub const STORAGE_KEY_BYTES: usize = 32;
13
14/// Size of storage values in bytes (32 bytes / 256 bits).
15pub const STORAGE_VALUE_BYTES: usize = 32;
16
17/// Maximum size of calldata in bytes (256 KB).
18pub const MAX_CALLDATA_SIZE: usize = 262_144;
19
20/// Maximum size of return data in bytes (256 KB).
21pub const MAX_RETURN_DATA_SIZE: usize = 262_144;
22
23/// Maximum size of log data in bytes (64 KB).
24pub const MAX_LOG_DATA_SIZE: usize = 65_536;
25
26/// Maximum number of topics per log event.
27pub const MAX_LOG_TOPICS: usize = 8;
28
29/// Oracle error codes returned by `http_call`.
30pub mod oracle_rc {
31    /// Memory allocation error.
32    pub const MEM_ERR: i32 = -1;
33    /// Encoding/decoding error.
34    pub const ENCODING_ERR: i32 = -2;
35    /// URL not in approved list.
36    pub const URL_NOT_APPROVED: i32 = -3;
37    /// Oracle request is pending.
38    pub const PENDING: i32 = -5;
39    /// Oracle request expired.
40    pub const EXPIRED: i32 = -6;
41    /// Response too large.
42    pub const RESPONSE_TOO_LARGE: i32 = -7;
43    /// Oracle depth limit exceeded.
44    pub const DEPTH_LIMIT_EXCEEDED: i32 = -8;
45}
46
47#[cfg(target_arch = "wasm32")]
48#[link(wasm_import_module = "env")]
49extern "C" {
50    fn storage_read(key_ptr: i32, key_len: i32, val_ptr: i32, val_len: i32) -> i32;
51    fn storage_write(key_ptr: i32, key_len: i32, val_ptr: i32, val_len: i32) -> i32;
52
53    fn get_caller(ptr: i32) -> i32;
54    fn get_owner(ptr: i32) -> i32;
55    fn get_contract_id(ptr: i32) -> i32;
56    fn get_height(ptr: i32) -> i32;
57    fn get_timestamp(ptr: i32) -> i32;
58    fn get_value(ptr: i32) -> i32;
59    fn get_calldata(ptr: i32) -> i32;
60
61    fn set_return_data(ptr: i32, len: i32) -> i32;
62    fn emit_log(topics_ptr: i32, topics_len: i32, data_ptr: i32, data_len: i32) -> i32;
63
64    fn call_contract(
65        contract_ptr: i32,
66        calldata_ptr: i32,
67        calldata_len: i32,
68        result_ptr: i32,
69    ) -> i32;
70    fn call_contract_v2(
71        contract_ptr: i32,
72        calldata_ptr: i32,
73        calldata_len: i32,
74        value_lo: i64,
75        value_hi: i64,
76        result_ptr: i32,
77        result_len: i32,
78    ) -> i32;
79
80    fn http_call(
81        url_ptr: i32,
82        url_len: i32,
83        method_ptr: i32,
84        method_len: i32,
85        body_ptr: i32,
86        body_len: i32,
87        result_ptr: i32,
88    ) -> i32;
89}
90
91#[cfg(not(target_arch = "wasm32"))]
92mod host_stub {
93    // Host stubs are intentionally explicit to make host-side tests fail loudly.
94    pub unsafe fn storage_read(_: i32, _: i32, _: i32, _: i32) -> i32 {
95        -127
96    }
97    pub unsafe fn storage_write(_: i32, _: i32, _: i32, _: i32) -> i32 {
98        -127
99    }
100
101    pub unsafe fn get_caller(_: i32) -> i32 {
102        -127
103    }
104    pub unsafe fn get_owner(_: i32) -> i32 {
105        -127
106    }
107    pub unsafe fn get_contract_id(_: i32) -> i32 {
108        -127
109    }
110    pub unsafe fn get_height(_: i32) -> i32 {
111        -127
112    }
113    pub unsafe fn get_timestamp(_: i32) -> i32 {
114        -127
115    }
116    pub unsafe fn get_value(_: i32) -> i32 {
117        -127
118    }
119    pub unsafe fn get_calldata(_: i32) -> i32 {
120        -127
121    }
122
123    pub unsafe fn set_return_data(_: i32, _: i32) -> i32 {
124        -127
125    }
126    pub unsafe fn emit_log(_: i32, _: i32, _: i32, _: i32) -> i32 {
127        -127
128    }
129
130    pub unsafe fn call_contract(_: i32, _: i32, _: i32, _: i32) -> i32 {
131        -127
132    }
133    pub unsafe fn call_contract_v2(_: i32, _: i32, _: i32, _: i64, _: i64, _: i32, _: i32) -> i32 {
134        -127
135    }
136
137    pub unsafe fn http_call(_: i32, _: i32, _: i32, _: i32, _: i32, _: i32, _: i32) -> i32 {
138        -127
139    }
140}
141
142#[cfg(not(target_arch = "wasm32"))]
143use host_stub::*;
144
145/// Checks a return code from a host function.
146///
147/// Returns `Ok(rc)` if non-negative, otherwise wraps in `Error`.
148#[inline]
149fn check_rc(rc: i32) -> Result<i32> {
150    if rc < 0 {
151        return Err(Error::new(rc));
152    }
153    Ok(rc)
154}
155
156/// Reads a 32-byte value from storage at the given 32-byte key.
157pub fn storage_read_32(key: &[u8; STORAGE_KEY_BYTES]) -> Result<[u8; STORAGE_VALUE_BYTES]> {
158    let mut out = [0u8; STORAGE_VALUE_BYTES];
159    let rc = unsafe {
160        storage_read(
161            key.as_ptr() as i32,
162            STORAGE_KEY_BYTES as i32,
163            out.as_mut_ptr() as i32,
164            STORAGE_VALUE_BYTES as i32,
165        )
166    };
167    check_rc(rc)?;
168    Ok(out)
169}
170
171/// Writes a 32-byte value to storage at the given 32-byte key.
172pub fn storage_write_32(
173    key: &[u8; STORAGE_KEY_BYTES],
174    value: &[u8; STORAGE_VALUE_BYTES],
175) -> Result<()> {
176    let rc = unsafe {
177        storage_write(
178            key.as_ptr() as i32,
179            STORAGE_KEY_BYTES as i32,
180            value.as_ptr() as i32,
181            STORAGE_VALUE_BYTES as i32,
182        )
183    };
184    check_rc(rc)?;
185    Ok(())
186}
187
188/// Retrieves the caller's address (32 bytes).
189pub fn get_caller_32() -> Result<[u8; STORAGE_KEY_BYTES]> {
190    let mut out = [0u8; STORAGE_KEY_BYTES];
191    let rc = unsafe { get_caller(out.as_mut_ptr() as i32) };
192    check_rc(rc)?;
193    Ok(out)
194}
195
196/// Retrieves the contract owner's address (32 bytes).
197pub fn get_owner_32() -> Result<[u8; STORAGE_KEY_BYTES]> {
198    let mut out = [0u8; STORAGE_KEY_BYTES];
199    let rc = unsafe { get_owner(out.as_mut_ptr() as i32) };
200    check_rc(rc)?;
201    Ok(out)
202}
203
204/// Retrieves the current contract's ID (32 bytes).
205pub fn get_contract_id_32() -> Result<[u8; STORAGE_KEY_BYTES]> {
206    let mut out = [0u8; STORAGE_KEY_BYTES];
207    let rc = unsafe { get_contract_id(out.as_mut_ptr() as i32) };
208    check_rc(rc)?;
209    Ok(out)
210}
211
212/// Retrieves the current block height.
213pub fn get_height_u64() -> Result<u64> {
214    let mut bytes = [0u8; 8];
215    let rc = unsafe { get_height(bytes.as_mut_ptr() as i32) };
216    check_rc(rc)?;
217    Ok(u64::from_le_bytes(bytes))
218}
219
220/// Retrieves the current block timestamp (Unix seconds).
221pub fn get_timestamp_u64() -> Result<u64> {
222    let mut bytes = [0u8; 8];
223    let rc = unsafe { get_timestamp(bytes.as_mut_ptr() as i32) };
224    check_rc(rc)?;
225    Ok(u64::from_le_bytes(bytes))
226}
227
228/// Retrieves the value sent with the current call (in smallest units).
229pub fn get_value_u128() -> Result<u128> {
230    let mut bytes = [0u8; 16];
231    let rc = unsafe { get_value(bytes.as_mut_ptr() as i32) };
232    check_rc(rc)?;
233    Ok(u128::from_le_bytes(bytes))
234}
235
236/// Reads the calldata into the provided buffer.
237///
238/// Returns the actual length of calldata written.
239pub fn read_calldata(buf: &mut [u8]) -> Result<usize> {
240    if buf.len() > MAX_CALLDATA_SIZE {
241        return Err(Error::new(-2));
242    }
243    let rc = unsafe { get_calldata(buf.as_mut_ptr() as i32) };
244    let len = check_rc(rc)? as usize;
245    Ok(len.min(buf.len()))
246}
247
248/// Sets the return data for the current contract call.
249pub fn set_return_data_bytes(bytes: &[u8]) -> Result<()> {
250    if bytes.len() > MAX_RETURN_DATA_SIZE {
251        return Err(Error::new(-2));
252    }
253    let rc = unsafe { set_return_data(bytes.as_ptr() as i32, bytes.len() as i32) };
254    check_rc(rc)?;
255    Ok(())
256}
257
258/// Emits a log event with topics and data.
259pub fn emit_log_bytes(flat_topics: &[u8], data: &[u8]) -> Result<()> {
260    if data.len() > MAX_LOG_DATA_SIZE {
261        return Err(Error::new(-2));
262    }
263    if flat_topics.len() > MAX_LOG_TOPICS * STORAGE_KEY_BYTES
264        || flat_topics.len() % STORAGE_KEY_BYTES != 0
265    {
266        return Err(Error::new(-2));
267    }
268    let rc = unsafe {
269        emit_log(
270            flat_topics.as_ptr() as i32,
271            flat_topics.len() as i32,
272            data.as_ptr() as i32,
273            data.len() as i32,
274        )
275    };
276    check_rc(rc)?;
277    Ok(())
278}
279
280/// Calls another contract (legacy version without value transfer).
281pub fn call_contract_legacy(
282    contract_id: &[u8; STORAGE_KEY_BYTES],
283    calldata: &[u8],
284    out: &mut [u8],
285) -> Result<usize> {
286    let rc = unsafe {
287        call_contract(
288            contract_id.as_ptr() as i32,
289            calldata.as_ptr() as i32,
290            calldata.len() as i32,
291            out.as_mut_ptr() as i32,
292        )
293    };
294    Ok(check_rc(rc)? as usize)
295}
296
297/// Calls another contract with value transfer (v2 API).
298pub fn call_contract_v2_with_value(
299    contract_id: &[u8; STORAGE_KEY_BYTES],
300    calldata: &[u8],
301    value: u128,
302    out: &mut [u8],
303) -> Result<usize> {
304    let lo = value as u64 as i64;
305    let hi = (value >> 64) as u64 as i64;
306    let rc = unsafe {
307        call_contract_v2(
308            contract_id.as_ptr() as i32,
309            calldata.as_ptr() as i32,
310            calldata.len() as i32,
311            lo,
312            hi,
313            out.as_mut_ptr() as i32,
314            out.len() as i32,
315        )
316    };
317    Ok(check_rc(rc)? as usize)
318}
319
320/// Makes an HTTP oracle call.
321///
322/// Returns the number of bytes written to `out`, or a negative error code.
323pub fn http_call_bytes(url: &[u8], method: &[u8], body: &[u8], out: &mut [u8]) -> Result<usize> {
324    let rc = unsafe {
325        http_call(
326            url.as_ptr() as i32,
327            url.len() as i32,
328            method.as_ptr() as i32,
329            method.len() as i32,
330            body.as_ptr() as i32,
331            body.len() as i32,
332            out.as_mut_ptr() as i32,
333        )
334    };
335    Ok(check_rc(rc)? as usize)
336}