darkfi/runtime/import/db/
db_set.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 darkfi_sdk::wasm;
20use darkfi_serial::Decodable;
21use tracing::error;
22use wasmer::{FunctionEnvMut, WasmPtr};
23
24use crate::runtime::{
25    import::{acl::acl_allow, util::wasm_mem_read},
26    vm_runtime::{ContractSection, Env},
27};
28
29/// Set a value in the on-chain database for the given DbHandle.
30///
31/// * `ptr` must contain the DbHandle index and the key-value pair.
32/// * The DbHandle must match the ContractId.
33///
34/// Returns `SUCCESS` on success, otherwise returns an error value.
35///
36/// ## Permissions
37/// * `ContractSection::Deploy`
38/// * `ContractSection::Update`
39pub(crate) fn db_set(ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, ptr_len: u32) -> i64 {
40    db_set_internal(ctx, ptr, ptr_len, false)
41}
42
43/// Set a value in the tx-local database for the given DbHandle.
44///
45/// * `ptr` must contain the DbHandle index and the key-value pair.
46/// * The DbHandle must match the ContractId.
47///
48/// Returns `SUCCESS` on success, otherwise returns an error value.
49///
50/// ## Permissions
51/// * `ContractSection::Deploy`
52/// * `ContractSection::Update`
53pub(crate) fn db_set_local(ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, ptr_len: u32) -> i64 {
54    db_set_internal(ctx, ptr, ptr_len, true)
55}
56
57/// Internal `db_set` function which branches to either on-chain or tx-local.
58///
59/// ## Permissions
60/// * `ContractSection::Deploy`
61/// * `ContractSection::Update`
62fn db_set_internal(
63    mut ctx: FunctionEnvMut<Env>,
64    ptr: WasmPtr<u8>,
65    ptr_len: u32,
66    local: bool,
67) -> i64 {
68    let lt = if local { "db_set_local" } else { "db_set" };
69    let (env, mut store) = ctx.data_and_store_mut();
70    let cid = env.contract_id;
71
72    // Enforce function ACL
73    if let Err(e) = acl_allow(env, &[ContractSection::Deploy, ContractSection::Update]) {
74        error!(
75            target: "runtime::db::{lt}",
76            "[WASM] [{cid}] {lt}(): Called in unauthorized section: {e}",
77        );
78        return darkfi_sdk::error::CALLER_ACCESS_DENIED
79    }
80
81    // Subtract used gas. Here we count the bytes written into the database.
82    // TODO: We might want to count only the difference in size if we're replacing
83    // data and the new data is larger.
84    env.subtract_gas(&mut store, ptr_len as u64);
85
86    // Get the wasm memory reader
87    let mut buf_reader = match wasm_mem_read(env, &store, ptr, ptr_len) {
88        Ok(v) => v,
89        Err(e) => {
90            error!(
91                target: "runtime::db::{lt}",
92                "[WASM] [{cid}] {lt}(): Failed to read wasm memory: {e}",
93            );
94            return darkfi_sdk::error::DB_SET_FAILED
95        }
96    };
97
98    // Decode DbHandle index
99    let db_handle_index: u32 = match Decodable::decode(&mut buf_reader) {
100        Ok(v) => v,
101        Err(e) => {
102            error!(
103                target: "runtime::db::{lt}",
104                "[WASM] [{cid}] {lt}(): Failed to decode DbHandle: {e}",
105            );
106            return darkfi_sdk::error::DB_SET_FAILED
107        }
108    };
109
110    let db_handle_index = db_handle_index as usize;
111
112    // If we're in ContractSection::Deploy, the zkas db handle is index 0.
113    // We should disallow writing with this.
114    if env.contract_section == ContractSection::Deploy && db_handle_index == 0 {
115        error!(
116            target: "runtime::db::{lt}",
117            "[WASM] [{cid}] {lt}(): Tried to write to zkas db",
118        );
119        return darkfi_sdk::error::CALLER_ACCESS_DENIED
120    }
121
122    // Decode key and value
123    let key: Vec<u8> = match Decodable::decode(&mut buf_reader) {
124        Ok(v) => v,
125        Err(e) => {
126            error!(
127                target: "runtime::db::{lt}",
128                "[WASM] [{cid}] {lt}(): Failed to decode key vec: {e}",
129            );
130            return darkfi_sdk::error::DB_SET_FAILED
131        }
132    };
133
134    let value: Vec<u8> = match Decodable::decode(&mut buf_reader) {
135        Ok(v) => v,
136        Err(e) => {
137            error!(
138                target: "runtime::db::{lt}",
139                "[WASM] [{cid}] {lt}(): Failed to decode value vec: {e}",
140            );
141            return darkfi_sdk::error::DB_SET_FAILED
142        }
143    };
144
145    // Make sure we've read the entire buffer
146    if buf_reader.position() != ptr_len as u64 {
147        error!(
148            target: "runtime::db::{lt}",
149            "[WASM] [{cid}] {lt}(): Trailing bytes in argument stream",
150        );
151        return darkfi_sdk::error::DB_SET_FAILED
152    }
153
154    // Fetch requested db handles
155    let db_handles = if local { env.local_db_handles.borrow() } else { env.db_handles.borrow() };
156
157    // Check DbHandle index is within bounds
158    if db_handles.len() <= db_handle_index {
159        error!(
160            target: "runtime::db::{lt}",
161            "[WASM] [{cid}] {lt}(): Requested DbHandle that is out of bounds",
162        );
163        return darkfi_sdk::error::DB_SET_FAILED
164    }
165
166    // Retrive DbHandle using the index
167    let db_handle = &db_handles[db_handle_index];
168
169    // Validate that the DbHandle matches the contract ID
170    if db_handle.contract_id != env.contract_id {
171        error!(
172            target: "runtime::db::{lt}",
173            "[WASM] [{cid}] {lt}(): Unauthorized to write to DbHandle",
174        );
175        return darkfi_sdk::error::CALLER_ACCESS_DENIED
176    }
177
178    // Insert key-value pair into the database corresponding to this contract
179    if local {
180        // Safe to unwrap here.
181        let mut db = env.tx_local.lock();
182        let db_cid = db.get_mut(&cid).unwrap();
183        let Some(tree) = db_cid.get_mut(&db_handle.tree) else {
184            error!(
185                target: "runtime::db::{lt}",
186                "[WASM] [{cid}] {lt}(): Could not insert to tx-local tree",
187            );
188            return darkfi_sdk::error::DB_SET_FAILED
189        };
190
191        tree.insert(key, value);
192    } else if env
193        .blockchain
194        .lock()
195        .unwrap()
196        .overlay
197        .lock()
198        .unwrap()
199        .insert(&db_handle.tree, &key, &value)
200        .is_err()
201    {
202        error!(
203            target: "runtime::db::{lt}",
204            "[WASM] [{cid}] {lt}(): Couldn't insert to on-chain tree",
205        );
206        return darkfi_sdk::error::DB_SET_FAILED
207    }
208
209    wasm::entrypoint::SUCCESS
210}