diff --git a/example/smart-contract/src/lib.rs b/example/smart-contract/src/lib.rs index d8597df53..08d6f5daa 100644 --- a/example/smart-contract/src/lib.rs +++ b/example/smart-contract/src/lib.rs @@ -1,10 +1,11 @@ use darkfi_sdk::{ + crypto::ContractId, db::{db_begin_tx, db_end_tx, db_get, db_init, db_lookup, db_set}, + define_contract, error::ContractResult, msg, state::set_update, tx::FuncCall, - define_contract, }; use darkfi_serial::{deserialize, serialize, SerialDecodable, SerialEncodable}; @@ -44,15 +45,11 @@ pub struct FooUpdate { pub age: u32, } -define_contract!( - init: init_contract, - exec: process_instruction, - apply: process_update -); +define_contract!(init: init_contract, exec: process_instruction, apply: process_update); -fn init_contract(_ix: &[u8]) -> ContractResult { +fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult { msg!("wakeup wagies!"); - db_init("wagies")?; + db_init(cid, "wagies")?; // Lets write a value in there let tx_handle = db_begin_tx()?; @@ -68,7 +65,7 @@ fn init_contract(_ix: &[u8]) -> ContractResult { // This is the main entrypoint function where the payload is fed. // Through here, you can branch out into different functions inside // this library. -fn process_instruction(ix: &[u8]) -> ContractResult { +fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult { match Function::from(ix[0]) { Function::Foo => { let tx_data = &ix[1..]; @@ -127,7 +124,7 @@ fn process_instruction(ix: &[u8]) -> ContractResult { Ok(()) } -fn process_update(update_data: &[u8]) -> ContractResult { +fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult { msg!("Make update!"); match Function::from(update_data[0]) { diff --git a/example/smart-contract/tests/runtime.rs b/example/smart-contract/tests/runtime.rs index d971efc2e..57a5e869b 100644 --- a/example/smart-contract/tests/runtime.rs +++ b/example/smart-contract/tests/runtime.rs @@ -19,11 +19,10 @@ use darkfi::{ blockchain::Blockchain, consensus::{TESTNET_GENESIS_HASH_BYTES, TESTNET_GENESIS_TIMESTAMP}, - crypto::contract_id::ContractId, runtime::vm_runtime::Runtime, Result, }; -use darkfi_sdk::{pasta::pallas, tx::FuncCall}; +use darkfi_sdk::{crypto::ContractId, pasta::pallas, tx::FuncCall}; use darkfi_serial::{serialize, Encodable, WriteExt}; use smart_contract::{FooCallData, Function}; diff --git a/src/blockchain/contractstore.rs b/src/blockchain/contractstore.rs index 6be92904f..8b8c228ed 100644 --- a/src/blockchain/contractstore.rs +++ b/src/blockchain/contractstore.rs @@ -16,10 +16,10 @@ * along with this program. If not, see . */ +use darkfi_sdk::crypto::ContractId; use darkfi_serial::deserialize; use crate::{ - crypto::contract_id::ContractId, Error::{ContractAlreadyInitialized, ContractNotFound, ContractStateNotFound}, Result, }; diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index 847061f97..69099fe09 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -34,9 +34,6 @@ pub use nfstore::NullifierStore; pub mod rootstore; pub use rootstore::RootStore; -pub mod statestore; -pub use statestore::StateStore; - pub mod txstore; pub use txstore::TxStore; diff --git a/src/blockchain/statestore.rs b/src/blockchain/statestore.rs deleted file mode 100644 index 7a1d73540..000000000 --- a/src/blockchain/statestore.rs +++ /dev/null @@ -1,63 +0,0 @@ -/* This file is part of DarkFi (https://dark.fi) - * - * Copyright (C) 2020-2022 Dyne.org foundation - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -use darkfi_serial::serialize; - -use crate::{crypto::contract_id::ContractId, Result}; - -const SLED_STATES_TREE: &[u8] = b"_states"; - -/// The `StateStore` is a `sled` tree storing states of deployed contracts. -/// The states themselves are data that is allocated and stored as raw bytes. -/// These bytes are (de)serialized by the code in wasm and the contracts can -/// operate on the state data themselves. Regarding on the (byte) size of the -/// state, the contract deployer should allocate and pay for a certain size of -/// their state stored by all the nodes. The cost should be linear to the byte -/// size used. -#[derive(Clone)] -pub struct StateStore(sled::Tree); - -impl StateStore { - /// Opens a new or existing `StateStore` on the given sled database. - pub fn new(db: &sled::Db) -> Result { - let tree = db.open_tree(SLED_STATES_TREE)?; - Ok(Self(tree)) - } - - /// Insert a state into the store. This will replace the previous state. - /// The contract's ID is used as a key, while the value is the contract - /// state serialized to bytes. - pub fn insert(&self, contract_id: &ContractId, contract_state: &[u8]) -> Result<()> { - self.0.insert(serialize(contract_id), contract_state.to_vec())?; - Ok(()) - } - - /// Check if the `StateStore` contains a state for the given `ContractId`. - pub fn contains(&self, contract_id: &ContractId) -> Result { - Ok(self.0.contains_key(serialize(contract_id))?) - } - - /// Retrieve a state from the `StateStore` given a `ContractId` if it exists. - pub fn get(&self, contract_id: &ContractId) -> Result>> { - if let Some(data) = self.0.get(serialize(contract_id))? { - return Ok(Some(data.to_vec())) - } - - Ok(None) - } -} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index e6c52c631..32c399041 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -30,9 +30,6 @@ pub mod token_list; pub mod types; pub mod util; -/// Contract ID definitions -pub mod contract_id; - /// VDF (Verifiable Delay Function) using MiMC pub mod mimc_vdf; diff --git a/src/runtime/import/db.rs b/src/runtime/import/db.rs index c1aed6c2b..9092ed553 100644 --- a/src/runtime/import/db.rs +++ b/src/runtime/import/db.rs @@ -16,13 +16,11 @@ * along with this program. If not, see . */ +use darkfi_sdk::crypto::ContractId; use log::error; use wasmer::{FunctionEnvMut, WasmPtr}; -use crate::{ - crypto::contract_id::ContractId, - runtime::vm_runtime::{ContractSection, Env}, -}; +use crate::runtime::vm_runtime::{ContractSection, Env}; /// Internal wasm runtime API for sled trees pub struct DbHandle { @@ -51,6 +49,26 @@ pub(crate) fn db_init(mut ctx: FunctionEnvMut, ptr: WasmPtr, len: u32) let contracts = &env.blockchain.contracts; let contract_id = &env.contract_id; + /* + let Ok(cid_slice) = cid_ptr.slice(&memory_view, 32) else { + error!(target: "wasm_runtime::db_init", "Failed to read contract id from ptr"); + return -2 + }; + + let Ok(cid_bytes) = cid_slice.read_to_vec() else { + error!(target: "wasm_runtime::db_init", "Failed to read slice to vec in db_init"); + return -2 + }; + + // FIXME: Could panic + let cid = ContractId::from_bytes(cid_bytes.try_into().unwrap()); + + if &cid != contract_id { + error!(target: "wasm_runtime::db_init", "Unauthorized ContractId for db_init"); + return -1 + } + */ + let Ok(db_name) = ptr.read_utf8_string(&memory_view, len) else { error!(target: "wasm_runtime::db_init", "Failed to read string from VM memory"); return -2 @@ -98,7 +116,6 @@ pub(crate) fn db_lookup(mut ctx: FunctionEnvMut, ptr: WasmPtr, len: u32 return -2 } } - 0 } _ => -1, } diff --git a/src/runtime/vm_runtime.rs b/src/runtime/vm_runtime.rs index 80c438630..fe8b60f87 100644 --- a/src/runtime/vm_runtime.rs +++ b/src/runtime/vm_runtime.rs @@ -21,7 +21,7 @@ use std::{ sync::Arc, }; -use darkfi_sdk::entrypoint; +use darkfi_sdk::{crypto::ContractId, entrypoint}; use log::{debug, info}; use wasmer::{ imports, wasmparser::Operator, AsStoreRef, CompilerConfig, Function, FunctionEnv, Instance, @@ -34,7 +34,7 @@ use wasmer_middlewares::{ }; use super::{import, import::db::DbHandle, memory::MemoryManipulation}; -use crate::{blockchain::Blockchain, crypto::contract_id::ContractId, Error, Result}; +use crate::{blockchain::Blockchain, Error, Result}; /// Name of the wasm linear memory in our guest module const MEMORY: &str = "memory"; @@ -231,7 +231,7 @@ impl Runtime { env_mut.contract_section = ContractSection::Deploy; // Serialize the payload for the format the wasm runtime is expecting. - let payload = Self::serialize_payload(payload); + let payload = Self::serialize_payload(&env_mut.contract_id, payload); // Allocate enough memory for the payload and copy it into the memory. let pages_required = payload.len() / WASM_PAGE_SIZE + 1; @@ -281,7 +281,7 @@ impl Runtime { env_mut.contract_section = ContractSection::Exec; // Serialize the payload for the format the wasm runtime is expecting. - let payload = Self::serialize_payload(payload); + let payload = Self::serialize_payload(&env_mut.contract_id, payload); // Allocate enough memory for the payload and copy it into the memory. let pages_required = payload.len() / WASM_PAGE_SIZE + 1; @@ -332,11 +332,12 @@ impl Runtime { // Take the update data from env, and serialize it for the format the wasm // runtime is expecting. + // FIXME: Can panic let update_data = env_mut.contract_update.take().unwrap(); let mut payload = Vec::with_capacity(1 + update_data.1.len()); payload.extend_from_slice(&[update_data.0]); payload.extend_from_slice(&update_data.1); - let payload = Self::serialize_payload(&payload); + let payload = Self::serialize_payload(&env_mut.contract_id, &payload); // Allocate enough memory for the payload and copy it into the memory. let pages_required = payload.len() / WASM_PAGE_SIZE + 1; @@ -378,7 +379,7 @@ impl Runtime { fn print_logs(&self) { let logs = self.ctx.as_ref(&self.store).logs.borrow(); for msg in logs.iter() { - debug!(target: "wasm_runtime::run", "Contract log: {}", msg); + debug!(target: "wasm_runtime::print_logs", "Contract log: {}", msg); } } @@ -425,9 +426,11 @@ impl Runtime { /// Serialize contract payload to the format accepted by the runtime functions. /// We keep the same payload as a slice of bytes, and prepend it with a /// little-endian u64 to tell the payload's length. - fn serialize_payload(payload: &[u8]) -> Vec { + fn serialize_payload(cid: &ContractId, payload: &[u8]) -> Vec { + let ser_cid = cid.to_bytes(); let payload_len = payload.len(); - let mut out = Vec::with_capacity(8 + payload_len); + let mut out = Vec::with_capacity(ser_cid.len() + 8 + payload_len); + out.extend_from_slice(&ser_cid); out.extend_from_slice(&(payload_len as u64).to_le_bytes()); out.extend_from_slice(payload); out diff --git a/src/sdk/src/crypto/contract_id.rs b/src/sdk/src/crypto/contract_id.rs new file mode 100644 index 000000000..304c5718b --- /dev/null +++ b/src/sdk/src/crypto/contract_id.rs @@ -0,0 +1,49 @@ +/* This file is part of DarkFi (https://dark.fi) + * + * Copyright (C) 2020-2022 Dyne.org foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use pasta_curves::{group::ff::PrimeField, pallas}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct ContractId(pallas::Base); + +impl ContractId { + pub fn new(contract_id: pallas::Base) -> Self { + Self(contract_id) + } + + pub fn inner(&self) -> pallas::Base { + self.0 + } + + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_repr() + } + + pub fn from_bytes(x: [u8; 32]) -> Self { + // FIXME: Handle Option + Self(pallas::Base::from_repr(x).unwrap()) + } +} + +impl std::fmt::Display for ContractId { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + // base58 encoding + let contractid: String = bs58::encode(self.0.to_repr()).into_string(); + write!(f, "{}", contractid) + } +} diff --git a/src/sdk/src/crypto/mod.rs b/src/sdk/src/crypto/mod.rs index 07278e38b..7124d598b 100644 --- a/src/sdk/src/crypto/mod.rs +++ b/src/sdk/src/crypto/mod.rs @@ -27,6 +27,13 @@ //! If you feel like trying, please help out with this migration, but do //! it properly, with care, and write documentation while you're at it. +/// Cryptographic constants +pub mod constants; + +/// Contract ID definitions and methods +pub mod contract_id; +pub use contract_id::ContractId; + /// Merkle node definitions pub mod merkle_node; pub use merkle_node::MerkleNode; @@ -37,6 +44,3 @@ pub use nullifier::Nullifier; /// Pedersen commitment utilities pub mod pedersen; - -/// Cryptographic constants -pub mod constants; diff --git a/src/sdk/src/db.rs b/src/sdk/src/db.rs index 67624c6b5..9d9bf22a9 100644 --- a/src/sdk/src/db.rs +++ b/src/sdk/src/db.rs @@ -1,4 +1,7 @@ -use super::error::{ContractError, GenericResult}; +use super::{ + crypto::ContractId, + error::{ContractError, GenericResult}, +}; type DbHandle = u32; type TxHandle = u32; @@ -9,19 +12,30 @@ type TxHandle = u32; /// type DbHandle = u32; /// db_init(db_name) -> DbHandle /// ``` -pub fn db_init(db_name: &str) -> GenericResult<()> { +pub fn db_init(contract_id: ContractId, db_name: &str) -> GenericResult { #[cfg(target_arch = "wasm32")] unsafe { - return match db_init_(db_name.as_ptr(), db_name.len() as u32) { - 0 => Ok(()), - -1 => Err(ContractError::CallerAccessDenied), - -2 => Err(ContractError::DbInitFailed), - _ => unreachable!(), + println!("sdk/src/db.rs:db_init() BEGIN"); + let ret = db_init_( + //contract_id.to_bytes().as_ptr(), + //32_u32, + db_name.as_ptr(), + db_name.len() as u32, + ); + + if ret < 0 { + match ret { + -1 => return Err(ContractError::CallerAccessDenied), + -2 => return Err(ContractError::DbInitFailed), + _ => unimplemented!(), + } } + + return Ok(ret as u32) } #[cfg(not(target_arch = "wasm32"))] - todo!("{}", db_name); + unimplemented!() } pub fn db_lookup(db_name: &str) -> GenericResult { diff --git a/src/sdk/src/entrypoint.rs b/src/sdk/src/entrypoint.rs index f87e47d75..60a76f5a8 100644 --- a/src/sdk/src/entrypoint.rs +++ b/src/sdk/src/entrypoint.rs @@ -18,6 +18,8 @@ use std::{mem::size_of, slice::from_raw_parts}; +use crate::crypto::ContractId; + /// Success exit code for a contract pub const SUCCESS: u64 = 0; @@ -27,27 +29,27 @@ macro_rules! define_contract { /// # Safety #[no_mangle] pub unsafe extern "C" fn __initialize(input: *mut u8) -> u64 { - let instruction_data = $crate::entrypoint::deserialize(input); + let (contract_id, instruction_data) = $crate::entrypoint::deserialize(input); - match $init_func(&instruction_data) { + match $init_func(contract_id, &instruction_data) { Ok(()) => $crate::entrypoint::SUCCESS, Err(e) => e.into(), } } #[no_mangle] pub unsafe extern "C" fn __entrypoint(input: *mut u8) -> u64 { - let instruction_data = $crate::entrypoint::deserialize(input); + let (contract_id, instruction_data) = $crate::entrypoint::deserialize(input); - match $exec_func(&instruction_data) { + match $exec_func(contract_id, &instruction_data) { Ok(()) => $crate::entrypoint::SUCCESS, Err(e) => e.into(), } } #[no_mangle] pub unsafe extern "C" fn __update(input: *mut u8) -> u64 { - let update_data = $crate::entrypoint::deserialize(input); + let (contract_id, update_data) = $crate::entrypoint::deserialize(input); - match $apply_func(&update_data) { + match $apply_func(contract_id, &update_data) { Ok(()) => $crate::entrypoint::SUCCESS, Err(e) => e.into(), } @@ -57,12 +59,16 @@ macro_rules! define_contract { /// Deserialize a given payload in `entrypoint` /// # Safety -pub unsafe fn deserialize<'a>(input: *mut u8) -> &'a [u8] { +pub unsafe fn deserialize<'a>(input: *mut u8) -> (ContractId, &'a [u8]) { let mut offset: usize = 0; + let contract_id_len = 32; + let contract_id_slice = { from_raw_parts(input.add(offset), contract_id_len) }; + offset += contract_id_len; + let instruction_data_len = *(input.add(offset) as *const u64) as usize; offset += size_of::(); let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) }; - instruction_data + (ContractId::from_bytes(contract_id_slice.try_into().unwrap()), instruction_data) }