darkfi/runtime/import/
util.rs

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