darkfi/runtime/import/
util.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::io::Cursor;
20
21use darkfi_sdk::wasm;
22use darkfi_serial::Decodable;
23use tracing::{debug, error};
24use wasmer::{FunctionEnvMut, StoreMut, WasmPtr};
25
26use super::acl::acl_allow;
27use crate::{
28    runtime::vm_runtime::{ContractSection, Env},
29    Result,
30};
31
32/// Host function for logging strings.
33pub(crate) fn drk_log(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u32) {
34    let (env, mut store) = ctx.data_and_store_mut();
35
36    // Subtract used gas. Here we count the length of the string.
37    env.subtract_gas(&mut store, len as u64);
38
39    let memory_view = env.memory_view(&store);
40    match ptr.read_utf8_string(&memory_view, len) {
41        Ok(msg) => {
42            let mut logs = env.logs.borrow_mut();
43            logs.push(msg);
44            std::mem::drop(logs);
45        }
46        Err(_) => {
47            error!(
48                target: "runtime::util::drk_log",
49                "[WASM] [{}] drk_log(): Failed to read UTF-8 string from VM memory",
50                env.contract_id,
51            );
52        }
53    }
54}
55
56/// Create a mem slice of the WASM VM memory given a pointer and its length,
57/// and return a `Cursor` from which callers are able to read as a stream.
58pub(crate) fn wasm_mem_read(
59    env: &Env,
60    store: &StoreMut<'_>,
61    ptr: WasmPtr<u8>,
62    ptr_len: u32,
63) -> Result<Cursor<Vec<u8>>> {
64    let memory_view = env.memory_view(&store);
65    let mem_slice = ptr.slice(&memory_view, ptr_len)?;
66
67    // Allocate a buffer and copy all the data from the pointer
68    // into the buffer
69    let mut buf = vec![0u8; ptr_len as usize];
70    mem_slice.read_slice(&mut buf)?;
71
72    // Once the data is copied, we'll return a Cursor over it
73    Ok(Cursor::new(buf))
74}
75
76/// Writes data to the `contract_return_data` field of [`Env`].
77/// The data will be read from `ptr` at a memory offset specified by `len`.
78///
79/// Returns `SUCCESS` on success, otherwise returns an error code corresponding
80/// to a [`darkfi_sdk::error::ContractError`].
81///
82/// Permissions: metadata, exec
83pub(crate) fn set_return_data(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u32) -> i64 {
84    let (env, mut store) = ctx.data_and_store_mut();
85    let cid = &env.contract_id;
86
87    // Enforce function ACL
88    if let Err(e) = acl_allow(env, &[ContractSection::Metadata, ContractSection::Exec]) {
89        error!(
90            target: "runtime::util::set_return_data",
91            "[WASM] [{cid}] set_return_data(): Called in unauthorized section: {e}"
92        );
93        return darkfi_sdk::error::CALLER_ACCESS_DENIED
94    }
95
96    // Subtract used gas. Here we count the length read from the memory slice.
97    env.subtract_gas(&mut store, len as u64);
98
99    let memory_view = env.memory_view(&store);
100    let Ok(slice) = ptr.slice(&memory_view, len) else { return darkfi_sdk::error::INTERNAL_ERROR };
101    let Ok(return_data) = slice.read_to_vec() else { return darkfi_sdk::error::INTERNAL_ERROR };
102
103    // This function should only ever be called once on the runtime.
104    if env.contract_return_data.take().is_some() {
105        return darkfi_sdk::error::SET_RETVAL_ERROR
106    }
107    env.contract_return_data.set(Some(return_data));
108
109    wasm::entrypoint::SUCCESS
110}
111
112/// Retrieve an object from the object store specified by the index `idx`.
113/// The object's data is written to `ptr`.
114///
115/// Returns `SUCCESS` on success and an error code otherwise.
116///
117/// Permissions: deploy, metadata, exec
118pub(crate) fn get_object_bytes(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, idx: u32) -> i64 {
119    // Get the slice, where we will read the size of the buffer
120    let (env, mut store) = ctx.data_and_store_mut();
121    let cid = env.contract_id;
122
123    // Enforce function ACL
124    if let Err(e) =
125        acl_allow(env, &[ContractSection::Deploy, ContractSection::Metadata, ContractSection::Exec])
126    {
127        error!(
128            target: "runtime::util::get_object_bytes",
129            "[WASM] [{cid}] get_object_bytes(): Called in unauthorized section: {e}"
130        );
131        return darkfi_sdk::error::CALLER_ACCESS_DENIED
132    }
133
134    // Get the object from env
135    let objects = env.objects.borrow();
136    if idx as usize >= objects.len() {
137        error!(
138            target: "runtime::util::get_object_bytes",
139            "[WASM] [{cid}] get_object_bytes(): Tried to access object out of bounds"
140        );
141        return darkfi_sdk::error::DATA_TOO_LARGE
142    }
143    let obj = objects[idx as usize].clone();
144    drop(objects);
145
146    if obj.len() > u32::MAX as usize {
147        return darkfi_sdk::error::DATA_TOO_LARGE
148    }
149
150    // Subtract used gas. Here we count the bytes written to the memory slice
151    env.subtract_gas(&mut store, obj.len() as u64);
152
153    // Read N bytes from the object and write onto the ptr.
154    let memory_view = env.memory_view(&store);
155    let Ok(slice) = ptr.slice(&memory_view, obj.len() as u32) else {
156        error!(
157            target: "runtime::util::get_object_bytes",
158            "[WASM] [{cid}] get_object_bytes(): Failed to make slice from ptr"
159        );
160        return darkfi_sdk::error::INTERNAL_ERROR
161    };
162
163    // Put the result in the VM
164    if let Err(e) = slice.write_slice(&obj) {
165        error!(
166            target: "runtime::util::get_object_bytes",
167            "[WASM] [{cid}] get_object_bytes(): Failed to write to memory slice: {e}"
168        );
169        return darkfi_sdk::error::INTERNAL_ERROR
170    };
171
172    wasm::entrypoint::SUCCESS
173}
174
175/// Returns the size (number of bytes) of an object in the object store
176/// specified by index `idx`.
177///
178/// Permissions: deploy, metadata, exec
179pub(crate) fn get_object_size(mut ctx: FunctionEnvMut<Env>, idx: u32) -> i64 {
180    // Get the slice, where we will read the size of the buffer
181    let (env, mut store) = ctx.data_and_store_mut();
182    let cid = env.contract_id;
183
184    // Enforce function ACL
185    if let Err(e) =
186        acl_allow(env, &[ContractSection::Deploy, ContractSection::Metadata, ContractSection::Exec])
187    {
188        error!(
189            target: "runtime::util::get_object_size",
190            "[WASM] [{cid}] get_object_size(): Called in unauthorized section: {e}"
191        );
192        return darkfi_sdk::error::CALLER_ACCESS_DENIED
193    }
194
195    // Get the object from env
196    let objects = env.objects.borrow();
197    if idx as usize >= objects.len() {
198        error!(
199            target: "runtime::util::get_object_size",
200            "[WASM] [{cid}] get_object_size(): Tried to access object out of bounds"
201        );
202        return darkfi_sdk::error::DATA_TOO_LARGE
203    }
204
205    let obj = &objects[idx as usize];
206    let obj_len = obj.len();
207    drop(objects);
208
209    if obj_len > u32::MAX as usize {
210        return darkfi_sdk::error::DATA_TOO_LARGE
211    }
212
213    // Subtract used gas. Here we count the size of the object.
214    // TODO: This could probably be fixed-cost
215    env.subtract_gas(&mut store, obj_len as u64);
216
217    obj_len as i64
218}
219
220/// Will return current runtime configured verifying block height number
221///
222/// Permissions: deploy, metadata, exec
223pub(crate) fn get_verifying_block_height(mut ctx: FunctionEnvMut<Env>) -> i64 {
224    let (env, mut store) = ctx.data_and_store_mut();
225    let cid = env.contract_id;
226
227    if let Err(e) =
228        acl_allow(env, &[ContractSection::Deploy, ContractSection::Metadata, ContractSection::Exec])
229    {
230        error!(
231            target: "runtime::util::get_verifying_block_height",
232            "[WASM] [{cid}] get_verifying_block_height(): Called in unauthorized section: {e}"
233        );
234        return darkfi_sdk::error::CALLER_ACCESS_DENIED
235    }
236
237    // Subtract used gas. Here we count the size of the object.
238    // u32 is 4 bytes.
239    env.subtract_gas(&mut store, 4);
240
241    env.verifying_block_height as i64
242}
243
244/// Will return currently configured block time target, in seconds
245///
246/// Permissions: deploy, metadata, exec
247pub(crate) fn get_block_target(mut ctx: FunctionEnvMut<Env>) -> i64 {
248    let (env, mut store) = ctx.data_and_store_mut();
249    let cid = env.contract_id;
250
251    if let Err(e) =
252        acl_allow(env, &[ContractSection::Deploy, ContractSection::Metadata, ContractSection::Exec])
253    {
254        error!(
255            target: "runtime::util::get_block_target",
256            "[WASM] [{cid}] get_block_target(): Called in unauthorized section: {e}"
257        );
258        return darkfi_sdk::error::CALLER_ACCESS_DENIED
259    }
260
261    // Subtract used gas. Here we count the size of the object.
262    // u32 is 4 bytes.
263    env.subtract_gas(&mut store, 4);
264
265    env.block_target as i64
266}
267
268/// Will return current runtime configured transaction hash
269///
270/// Permissions: deploy, metadata, exec
271pub(crate) fn get_tx_hash(mut ctx: FunctionEnvMut<Env>) -> i64 {
272    let (env, mut store) = ctx.data_and_store_mut();
273    let cid = env.contract_id;
274
275    if let Err(e) =
276        acl_allow(env, &[ContractSection::Deploy, ContractSection::Metadata, ContractSection::Exec])
277    {
278        error!(
279            target: "runtime::util::get_tx_hash",
280            "[WASM] [{cid}] get_tx_hash(): Called in unauthorized section: {e}"
281        );
282        return darkfi_sdk::error::CALLER_ACCESS_DENIED
283    }
284
285    // Subtract used gas. Here we count the size of the object.
286    env.subtract_gas(&mut store, 32);
287
288    // Return the length of the objects Vector.
289    // This is the location of the data that was retrieved and pushed
290    let mut objects = env.objects.borrow_mut();
291    objects.push(env.tx_hash.inner().to_vec());
292    (objects.len() - 1) as i64
293}
294
295/// Will return current runtime configured verifying block height number
296///
297/// Permissions: deploy, metadata, exec
298pub(crate) fn get_call_index(mut ctx: FunctionEnvMut<Env>) -> i64 {
299    let (env, mut store) = ctx.data_and_store_mut();
300    let cid = env.contract_id;
301
302    if let Err(e) =
303        acl_allow(env, &[ContractSection::Deploy, ContractSection::Metadata, ContractSection::Exec])
304    {
305        error!(
306            target: "runtime::util::get_call_index",
307            "[WASM] [{cid}] get_call_index(): Called in unauthorized section: {e}"
308        );
309        return darkfi_sdk::error::CALLER_ACCESS_DENIED
310    }
311
312    // Subtract used gas. Here we count the size of the object.
313    // u8 is 1 byte.
314    env.subtract_gas(&mut store, 1);
315
316    env.call_idx as i64
317}
318
319/// Will return current blockchain timestamp,
320/// defined as the last block's timestamp.
321///
322/// Permissions: deploy, metadata, exec
323pub(crate) fn get_blockchain_time(mut ctx: FunctionEnvMut<Env>) -> i64 {
324    let (env, mut store) = ctx.data_and_store_mut();
325    let cid = &env.contract_id;
326
327    if let Err(e) =
328        acl_allow(env, &[ContractSection::Deploy, ContractSection::Metadata, ContractSection::Exec])
329    {
330        error!(
331            target: "runtime::util::get_blockchain_time",
332            "[WASM] [{cid}] get_blockchain_time(): Called in unauthorized section: {e}"
333        );
334        return darkfi_sdk::error::CALLER_ACCESS_DENIED
335    }
336
337    // Grab current last block
338    let timestamp = match env.blockchain.lock().unwrap().last_block_timestamp() {
339        Ok(b) => b,
340        Err(e) => {
341            error!(
342                target: "runtime::util::get_blockchain_time",
343                "[WASM] [{cid}] get_blockchain_time(): Internal error getting from blocks tree: {e}"
344            );
345            return darkfi_sdk::error::DB_GET_FAILED
346        }
347    };
348
349    // Subtract used gas. Here we count the size of the object.
350    // u64 is 8 bytes.
351    env.subtract_gas(&mut store, 8);
352
353    // Create the return object
354    let mut ret = Vec::with_capacity(8);
355    ret.extend_from_slice(&timestamp.inner().to_be_bytes());
356
357    // Copy Vec<u8> to the VM
358    let mut objects = env.objects.borrow_mut();
359    objects.push(ret.to_vec());
360    if objects.len() > u32::MAX as usize {
361        return darkfi_sdk::error::DATA_TOO_LARGE
362    }
363
364    (objects.len() - 1) as i64
365}
366
367/// Grabs last block from the `Blockchain` overlay and then copies its
368/// height to the VM's object store.
369///
370/// On success, returns the index of the new object in the object store.
371/// Otherwise, returns an error code.
372///
373/// Permissions: deploy, metadata, exec
374pub(crate) fn get_last_block_height(mut ctx: FunctionEnvMut<Env>) -> i64 {
375    let (env, mut store) = ctx.data_and_store_mut();
376    let cid = &env.contract_id;
377
378    // Enforce function ACL
379    if let Err(e) =
380        acl_allow(env, &[ContractSection::Deploy, ContractSection::Metadata, ContractSection::Exec])
381    {
382        error!(
383            target: "runtime::util::get_last_block_height",
384            "[WASM] [{cid}] get_last_block_height(): Called in unauthorized section: {e}"
385        );
386        return darkfi_sdk::error::CALLER_ACCESS_DENIED
387    }
388
389    // Grab current last block height
390    let height = match env.blockchain.lock().unwrap().last_block_height() {
391        Ok(b) => b,
392        Err(e) => {
393            error!(
394                target: "runtime::util::get_last_block_height",
395                "[WASM] [{cid}] get_last_block_height(): Internal error getting from blocks tree: {e}"
396            );
397            return darkfi_sdk::error::DB_GET_FAILED
398        }
399    };
400
401    // Subtract used gas. Here we count the size of the object.
402    // u64 is 8 bytes.
403    env.subtract_gas(&mut store, 8);
404
405    // Create the return object
406    let mut ret = Vec::with_capacity(8);
407    ret.extend_from_slice(&darkfi_serial::serialize(&height));
408
409    // Copy Vec<u8> to the VM
410    let mut objects = env.objects.borrow_mut();
411    objects.push(ret.to_vec());
412    if objects.len() > u32::MAX as usize {
413        return darkfi_sdk::error::DATA_TOO_LARGE
414    }
415
416    (objects.len() - 1) as i64
417}
418
419/// Reads a transaction by hash from the transactions store.
420///
421/// This function can be called from the Exec or Metadata [`ContractSection`].
422///
423/// On success, returns the length of the transaction bytes vector in the environment.
424/// Otherwise, returns an error code.
425///
426/// Permissions: deploy, metadata, exec
427pub(crate) fn get_tx(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>) -> i64 {
428    let (env, mut store) = ctx.data_and_store_mut();
429    let cid = env.contract_id;
430
431    if let Err(e) =
432        acl_allow(env, &[ContractSection::Deploy, ContractSection::Metadata, ContractSection::Exec])
433    {
434        error!(
435            target: "runtime::util::get_tx",
436            "[WASM] [{cid}] get_tx(): Called in unauthorized section: {e}"
437        );
438        return darkfi_sdk::error::CALLER_ACCESS_DENIED
439    }
440
441    // Subtract used gas. Here we count the length of the looked-up hash.
442    env.subtract_gas(&mut store, blake3::OUT_LEN as u64);
443
444    // Ensure that it is possible to read memory
445    let memory_view = env.memory_view(&store);
446    let Ok(mem_slice) = ptr.slice(&memory_view, blake3::OUT_LEN as u32) else {
447        error!(
448            target: "runtime::util::get_tx",
449            "[WASM] [{cid}] get_tx(): Failed to make slice from ptr"
450        );
451        return darkfi_sdk::error::DB_GET_FAILED
452    };
453
454    let mut buf = vec![0_u8; blake3::OUT_LEN];
455    if let Err(e) = mem_slice.read_slice(&mut buf) {
456        error!(
457            target: "runtime::util::get_tx",
458            "[WASM] [{cid}] get_tx(): Failed to read from memory slice: {e}"
459        );
460        return darkfi_sdk::error::DB_GET_FAILED
461    };
462
463    let mut buf_reader = Cursor::new(buf);
464
465    // Decode hash bytes for transaction that we wish to retrieve
466    let hash: [u8; blake3::OUT_LEN] = match Decodable::decode(&mut buf_reader) {
467        Ok(v) => v,
468        Err(e) => {
469            error!(
470                target: "runtime::util::get_tx",
471                "[WASM] [{cid}] get_tx(): Failed to decode hash from vec: {e}"
472            );
473            return darkfi_sdk::error::DB_GET_FAILED
474        }
475    };
476
477    // Make sure there are no trailing bytes in the buffer. This means we've used all data that was
478    // supplied.
479    if buf_reader.position() != blake3::OUT_LEN as u64 {
480        error!(
481            target: "runtime::util::get_tx",
482            "[WASM] [{cid}] get_tx(): Trailing bytes in argument stream"
483        );
484        return darkfi_sdk::error::DB_GET_FAILED
485    }
486
487    // Retrieve transaction using the `hash`
488    let ret = match env.blockchain.lock().unwrap().transactions.get_raw(&hash) {
489        Ok(v) => v,
490        Err(e) => {
491            error!(
492                target: "runtime::util::get_tx",
493                "[WASM] [{cid}] get_tx(): Internal error getting from tree: {e}"
494            );
495            return darkfi_sdk::error::DB_GET_FAILED
496        }
497    };
498
499    // Return special error if the data is empty
500    let Some(return_data) = ret else {
501        debug!(
502            target: "runtime::util::get_tx",
503            "[WASM] [{cid}] get_tx(): Return data is empty"
504        );
505        return darkfi_sdk::error::DB_GET_EMPTY
506    };
507
508    if return_data.len() > u32::MAX as usize {
509        return darkfi_sdk::error::DATA_TOO_LARGE
510    }
511
512    // Subtract used gas. Here we count the length of the data read from db.
513    env.subtract_gas(&mut store, return_data.len() as u64);
514
515    // Copy the data (Vec<u8>) to the VM by pushing it to the objects Vector.
516    let mut objects = env.objects.borrow_mut();
517    if objects.len() == u32::MAX as usize {
518        return darkfi_sdk::error::DATA_TOO_LARGE
519    }
520
521    // Return the length of the objects Vector.
522    // This is the location of the data that was retrieved and pushed
523    objects.push(return_data.to_vec());
524    (objects.len() - 1) as i64
525}
526
527/// Reads a transaction location by hash from the transactions store.
528///
529/// This function can be called from the Exec or Metadata [`ContractSection`].
530///
531/// On success, returns the length of the transaction location bytes vector in
532/// the environment. Otherwise, returns an error code.
533///
534/// Permissions: deploy, metadata, exec
535pub(crate) fn get_tx_location(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>) -> i64 {
536    let (env, mut store) = ctx.data_and_store_mut();
537    let cid = env.contract_id;
538
539    if let Err(e) =
540        acl_allow(env, &[ContractSection::Deploy, ContractSection::Metadata, ContractSection::Exec])
541    {
542        error!(
543            target: "runtime::util::get_tx_location",
544            "[WASM] [{cid}] get_tx_location(): Called in unauthorized section: {e}"
545        );
546        return darkfi_sdk::error::CALLER_ACCESS_DENIED
547    }
548
549    // Subtract used gas. Here we count the length of the looked-up hash.
550    env.subtract_gas(&mut store, blake3::OUT_LEN as u64);
551
552    // Ensure that it is possible to read memory
553    let memory_view = env.memory_view(&store);
554    let Ok(mem_slice) = ptr.slice(&memory_view, blake3::OUT_LEN as u32) else {
555        error!(
556            target: "runtime::util::get_tx_location",
557            "[WASM] [{cid}] get_tx_location(): Failed to make slice from ptr"
558        );
559        return darkfi_sdk::error::DB_GET_FAILED
560    };
561
562    let mut buf = vec![0_u8; blake3::OUT_LEN];
563    if let Err(e) = mem_slice.read_slice(&mut buf) {
564        error!(
565            target: "runtime::util::get_tx_location",
566            "[WASM] [{cid}] get_tx_location(): Failed to read from memory slice: {e}"
567        );
568        return darkfi_sdk::error::DB_GET_FAILED
569    };
570
571    let mut buf_reader = Cursor::new(buf);
572
573    // Decode hash bytes for transaction that we wish to retrieve
574    let hash: [u8; blake3::OUT_LEN] = match Decodable::decode(&mut buf_reader) {
575        Ok(v) => v,
576        Err(e) => {
577            error!(
578                target: "runtime::util::get_tx_location",
579                "[WASM] [{cid}] get_tx_location(): Failed to decode hash from vec: {e}"
580            );
581            return darkfi_sdk::error::DB_GET_FAILED
582        }
583    };
584
585    // Make sure there are no trailing bytes in the buffer. This means we've used all data that was
586    // supplied.
587    if buf_reader.position() != blake3::OUT_LEN as u64 {
588        error!(
589            target: "runtime::util::get_tx_location",
590            "[WASM] [{cid}] get_tx_location(): Trailing bytes in argument stream"
591        );
592        return darkfi_sdk::error::DB_GET_FAILED
593    }
594
595    // Retrieve transaction using the `hash`
596    let ret = match env.blockchain.lock().unwrap().transactions.get_location_raw(&hash) {
597        Ok(v) => v,
598        Err(e) => {
599            error!(
600                target: "runtime::util::get_tx_location",
601                "[WASM] [{cid}] get_tx_location(): Internal error getting from tree: {e}"
602            );
603            return darkfi_sdk::error::DB_GET_FAILED
604        }
605    };
606
607    // Return special error if the data is empty
608    let Some(return_data) = ret else {
609        debug!(
610            target: "runtime::util::get_tx_location",
611            "[WASM] [{cid}] get_tx_location(): Return data is empty"
612        );
613        return darkfi_sdk::error::DB_GET_EMPTY
614    };
615
616    if return_data.len() > u32::MAX as usize {
617        return darkfi_sdk::error::DATA_TOO_LARGE
618    }
619
620    // Subtract used gas. Here we count the length of the data read from db.
621    env.subtract_gas(&mut store, return_data.len() as u64);
622
623    // Copy the data (Vec<u8>) to the VM by pushing it to the objects Vector.
624    let mut objects = env.objects.borrow_mut();
625    if objects.len() == u32::MAX as usize {
626        return darkfi_sdk::error::DATA_TOO_LARGE
627    }
628
629    // Return the length of the objects Vector.
630    // This is the location of the data that was retrieved and pushed
631    objects.push(return_data.to_vec());
632    (objects.len() - 1) as i64
633}