Skip to content

Commit

Permalink
Merge branch 'main' into safe_ffi
Browse files Browse the repository at this point in the history
  • Loading branch information
iFrostizz authored Apr 29, 2024
2 parents 9f60af3 + 76f8132 commit e50e3e1
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 43 deletions.
8 changes: 1 addition & 7 deletions x/programs/rust/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --target-dir $CARGO_TARGET_DIR --release
```

- Optionally use our build script.

```sh
./scripts/build.sh
```

## Debugging

While developing programs you can optionally compile the program with `Wasi`
Expand All @@ -29,7 +23,7 @@ set `WithEnableTestingOnlyMode` for the runtime `Config`.
from your program and recompile without `DEBUG`.

```sh
DEBUG=1 ./scripts/build.sh
cargo build --target wasm32-wasi
```

## Storage
Expand Down
2 changes: 1 addition & 1 deletion x/programs/rust/examples/counter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub fn initialize_address(context: Context, address: Address) -> bool {

if program
.state()
.get::<i64, _>(StateKeys::Counter(address))
.get::<i64>(StateKeys::Counter(address))
.is_ok()
{
panic!("counter already initialized for address")
Expand Down
6 changes: 3 additions & 3 deletions x/programs/rust/examples/token/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub fn mint_to(context: Context, recipient: Address, amount: i64) -> bool {
let Context { program } = context;
let balance = program
.state()
.get::<i64, _>(StateKey::Balance(recipient))
.get::<i64>(StateKey::Balance(recipient))
.unwrap_or_default();

program
Expand Down Expand Up @@ -90,14 +90,14 @@ pub fn transfer(context: Context, sender: Address, recipient: Address, amount: i
// ensure the sender has adequate balance
let sender_balance = program
.state()
.get::<i64, _>(StateKey::Balance(sender))
.get::<i64>(StateKey::Balance(sender))
.expect("failed to update balance");

assert!(amount >= 0 && sender_balance >= amount, "invalid input");

let recipient_balance = program
.state()
.get::<i64, _>(StateKey::Balance(recipient))
.get::<i64>(StateKey::Balance(recipient))
.unwrap_or_default();

// update balances
Expand Down
14 changes: 7 additions & 7 deletions x/programs/rust/sdk_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use syn::{
PatType, Path, Type, Visibility,
};

const CONEXT_TYPE: &str = "wasmlanche_sdk::Context";
const CONTEXT_TYPE: &str = "wasmlanche_sdk::Context";

/// An attribute procedural macro that makes a function visible to the VM host.
/// It does so by wrapping the `item` tokenstream in a new function that can be called by the host.
Expand Down Expand Up @@ -43,19 +43,19 @@ pub fn public(_: TokenStream, item: TokenStream) -> TokenStream {
Some(FnArg::Typed(PatType { ty, .. })) => {
syn::Error::new(
ty.span(),
format!("The first paramter of a function with the `#[public]` attribute must be of type `{CONEXT_TYPE}`"),
format!("The first paramter of a function with the `#[public]` attribute must be of type `{CONTEXT_TYPE}`"),
)
}
Some(_) => {
syn::Error::new(
arg.span(),
format!("The first paramter of a function with the `#[public]` attribute must be of type `{CONEXT_TYPE}`"),
format!("The first paramter of a function with the `#[public]` attribute must be of type `{CONTEXT_TYPE}`"),
)
}
None => {
syn::Error::new(
input.sig.paren_token.span.join(),
format!("Functions with the `#[public]` attribute must have at least one parameter and the first parameter must be of type `{CONEXT_TYPE}`"),
format!("Functions with the `#[public]` attribute must have at least one parameter and the first parameter must be of type `{CONTEXT_TYPE}`"),
)
}
};
Expand Down Expand Up @@ -127,7 +127,7 @@ pub fn public(_: TokenStream, item: TokenStream) -> TokenStream {

// Extract the original function's return type. This must be a WASM supported type.
let return_type = &input.sig.output;
let context_type: Path = parse_str(CONEXT_TYPE).unwrap();
let context_type: Path = parse_str(CONTEXT_TYPE).unwrap();
let output = quote! {
// Need to include the original function in the output, so contract can call itself
#input
Expand Down Expand Up @@ -155,7 +155,7 @@ pub fn state_keys(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut item_enum = parse_macro_input!(item as ItemEnum);
// add default attributes
item_enum.attrs.push(syn::parse_quote! {
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
});
item_enum.attrs.push(syn::parse_quote! {
#[repr(u8)]
Expand Down Expand Up @@ -220,7 +220,7 @@ fn generate_to_vec(
/// Returns whether the type_path represents a Program type.
fn is_context(type_path: &std::boxed::Box<Type>) -> bool {
if let Type::Path(type_path) = type_path.as_ref() {
type_path.path.segments.last() == parse_str::<Path>(CONEXT_TYPE).unwrap().segments.last()
type_path.path.segments.last() == parse_str::<Path>(CONTEXT_TYPE).unwrap().segments.last()
} else {
false
}
Expand Down
8 changes: 7 additions & 1 deletion x/programs/rust/wasmlanche-sdk/src/program.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use std::hash::Hash;

use borsh::{BorshDeserialize, BorshSerialize};

use crate::state::Key;
use crate::{memory::to_host_ptr, state::Error as StateError, state::State, Params};

/// Represents the current Program in the context of the caller. Or an external
Expand All @@ -25,7 +28,10 @@ impl Program {
/// Returns a State object that can be used to interact with persistent
/// storage exposed by the host.
#[must_use]
pub fn state(&self) -> State {
pub fn state<K>(&self) -> State<K>
where
K: Into<Key> + Hash + PartialEq + Eq + Clone,
{
State::new(Program::new(*self.id()))
}

Expand Down
86 changes: 63 additions & 23 deletions x/programs/rust/wasmlanche-sdk/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{memory::from_host_ptr, program::Program};
use borsh::{BorshDeserialize, BorshSerialize};
use std::ops::Deref;
use crate::{from_host_ptr, program::Program, state::Error as StateError};
use borsh::{from_slice, to_vec, BorshDeserialize, BorshSerialize};
use std::{collections::HashMap, hash::Hash, ops::Deref};

#[derive(Clone, thiserror::Error, Debug)]
pub enum Error {
Expand Down Expand Up @@ -38,27 +38,51 @@ pub enum Error {
Delete,
}

pub struct State {
pub struct State<K>
where
K: Into<Key> + Hash + PartialEq + Eq + Clone,
{
program: Program,
cache: HashMap<K, Vec<u8>>,
}

impl State {
impl<K> Drop for State<K>
where
K: Into<Key> + Hash + PartialEq + Eq + Clone,
{
fn drop(&mut self) {
if !self.cache.is_empty() {
// force flush
self.flush().unwrap();
}
}
}

impl<K> State<K>
where
K: Into<Key> + Hash + PartialEq + Eq + Clone,
{
#[must_use]
pub fn new(program: Program) -> Self {
Self { program }
Self {
program,
cache: HashMap::new(),
}
}

/// Store a key and value to the host storage. If the key already exists,
/// the value will be overwritten.
/// # Errors
/// Returns an [Error] if the key or value cannot be
/// serialized or if the host fails to handle the operation.
pub fn store<K, V>(&self, key: K, value: &V) -> Result<(), Error>
pub fn store<V>(&mut self, key: K, value: &V) -> Result<(), Error>
where
V: BorshSerialize,
K: Into<Key>,
{
unsafe { host::put_bytes(&self.program, &key.into(), value) }
let serialized = to_vec(&value).map_err(|_| StateError::Deserialization)?;
self.cache.insert(key, serialized);

Ok(())
}

/// Get a value from the host's storage.
Expand All @@ -71,30 +95,46 @@ impl State {
/// the host fails to read the key and value.
/// # Panics
/// Panics if the value cannot be converted from i32 to usize.
pub fn get<T, K>(&self, key: K) -> Result<T, Error>
pub fn get<V>(&mut self, key: K) -> Result<V, Error>
where
K: Into<Key>,
T: BorshDeserialize,
V: BorshDeserialize,
{
let val_ptr = unsafe { host::get_bytes(&self.program, &key.into())? };
if val_ptr < 0 {
return Err(Error::Read);
}

// Wrap in OK for now, change from_raw_ptr to return Result
from_host_ptr(val_ptr)
let val_bytes = if let Some(val) = self.cache.get(&key) {
val
} else {
let val_ptr = unsafe { host::get_bytes(&self.program, &key.clone().into())? };
if val_ptr < 0 {
return Err(Error::Read);
}

// TODO Wrap in OK for now, change from_raw_ptr to return Result
let bytes = from_host_ptr(val_ptr)?;
self.cache.entry(key).or_insert(bytes)
};

from_slice::<V>(val_bytes).map_err(|_| StateError::Deserialization)
}

/// Delete a value from the hosts's storage.
/// # Errors
/// Returns an [Error] if the key cannot be serialized
/// or if the host fails to delete the key and the associated value
pub fn delete<K>(&self, key: K) -> Result<(), Error>
where
K: Into<Key>,
{
pub fn delete(&mut self, key: K) -> Result<(), Error> {
self.cache.remove(&key);

unsafe { host::delete_bytes(&self.program, &key.into()) }
}

/// Apply all pending operations to storage and mark the cache as flushed
fn flush(&mut self) -> Result<(), Error> {
for (key, value) in self.cache.drain() {
unsafe {
host::put_bytes(&self.program, &key.into(), &value)?;
}
}

Ok(())
}
}

/// Key is a wrapper around a `Vec<u8>` that represents a key in the host storage.
Expand Down
2 changes: 1 addition & 1 deletion x/programs/rust/wasmlanche-sdk/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize};

/// A struct that enforces a fixed length of 32 bytes which represents an address.
#[derive(Clone, Copy, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize, Hash)]
pub struct Address([u8; Self::LEN]);

impl Address {
Expand Down

0 comments on commit e50e3e1

Please sign in to comment.