Skip to content

Commit

Permalink
Use git dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
krl committed Mar 19, 2024
1 parent 3e315fb commit 363b9be
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 8 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions clar2wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ edition = "2021"
build = "build.rs"

[dependencies]
clarity = { path = "../../stacks-core/clarity" }
clarity = { git = "https://github.com/stacks-network/stacks-core.git", branch = "feat/clarity-wasm-next" }
clap = { version = "4.3.17", features = ["derive"] }
regex = "1.9.1"
walrus = "0.20.1"
lazy_static = "1.4.0"
wasmtime = "15.0.0"
stacks-common = { path = "../../stacks-core/stacks-common" }
stacks-common = { git = "https://github.com/stacks-network/stacks-core.git", branch = "feat/clarity-wasm-next" }

# For developer mode
sha2 = { version = "0.10.7", optional = true }
Expand Down
24 changes: 24 additions & 0 deletions clar2wasm/src/initialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,30 @@ impl<'a, 'b> ClarityWasmContext<'a, 'b> {
}
}

pub fn new_run(
global_context: &'a mut GlobalContext<'b>,
contract_context: &'a ContractContext,
call_stack: &'a mut CallStack,
sender: Option<PrincipalData>,
caller: Option<PrincipalData>,
sponsor: Option<PrincipalData>,
contract_analysis: Option<&'a ContractAnalysis>,
) -> Self {
ClarityWasmContext {
global_context,
contract_context: Some(contract_context),
contract_context_mut: None,
call_stack,
sender,
caller,
sponsor,
sender_stack: vec![],
caller_stack: vec![],
bhh_stack: vec![],
contract_analysis,
}
}

pub fn push_sender(&mut self, sender: PrincipalData) {
if let Some(current) = self.sender.take() {
self.sender_stack.push(current);
Expand Down
4 changes: 2 additions & 2 deletions clar2wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ pub use walrus::Module;
use wasm_generator::{GeneratorError, WasmGenerator};

mod deserialize;
mod initialize;
pub mod initialize;
mod linker;
mod serialize;
pub mod wasm_generator;
mod wasm_utils;
pub mod wasm_utils;
mod words;

#[cfg(feature = "developer-mode")]
Expand Down
298 changes: 296 additions & 2 deletions clar2wasm/src/wasm_utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#![allow(non_camel_case_types)]

use std::borrow::BorrowMut;

use clarity::vm::analysis::CheckErrors;
use clarity::vm::contexts::GlobalContext;
use clarity::vm::errors::{Error, WasmError};
use clarity::vm::types::{
ASCIIData, BuffData, CharType, ListData, OptionalData, PrincipalData,
Expand All @@ -9,10 +12,14 @@ use clarity::vm::types::{
use clarity::vm::types::{
BufferLength, SequenceSubtype, SequencedValue, StringSubtype, TypeSignature,
};
use clarity::vm::ContractName;
use clarity::vm::CallStack;
use clarity::vm::Value;
use clarity::vm::{ContractContext, ContractName};
use stacks_common::types::StacksEpochId;
use wasmtime::{AsContextMut, Memory, Val, ValType};
use wasmtime::{AsContextMut, Engine, Linker, Memory, Module, Store, Val, ValType};

use crate::initialize::ClarityWasmContext;
use crate::linker::link_host_functions;

#[allow(non_snake_case)]
pub enum MintAssetErrorCodes {
Expand Down Expand Up @@ -1168,3 +1175,290 @@ fn clar2wasm_ty(ty: &TypeSignature) -> Vec<ValType> {
_ => unimplemented!("{:?}", ty),
}
}

/// Call a function in the contract.
pub fn call_function<'a, 'b, 'c>(
function_name: &str,
args: &[Value],
global_context: &'a mut GlobalContext<'b>,
contract_context: &'a ContractContext,
call_stack: &'a mut CallStack,
sender: Option<PrincipalData>,
caller: Option<PrincipalData>,
sponsor: Option<PrincipalData>,
) -> Result<Value, Error> {
let epoch = global_context.epoch_id;
let context = ClarityWasmContext::new_run(
global_context,
contract_context,
call_stack,
sender,
caller,
sponsor,
None,
);

let func_types = context
.contract_context()
.lookup_function(function_name)
.ok_or(CheckErrors::UndefinedFunction(function_name.to_string()))?;
let engine = Engine::default();
let module = context
.contract_context()
.with_wasm_module(|wasm_module| unsafe {
Module::deserialize(&engine, wasm_module)
.map_err(|e| Error::Wasm(WasmError::UnableToLoadModule(e)))
})?;
let mut store = Store::new(&engine, context);
let mut linker = Linker::new(&engine);

// Link in the host interface functions.
link_host_functions(&mut linker)?;

let instance = linker
.instantiate(&mut store, &module)
.map_err(|e| Error::Wasm(WasmError::UnableToLoadModule(e)))?;

// Call the specified function
let func = instance
.get_func(&mut store, function_name)
.ok_or(CheckErrors::UndefinedFunction(function_name.to_string()))?;

// Access the global stack pointer from the instance
let stack_pointer = instance
.get_global(&mut store, "stack-pointer")
.ok_or(Error::Wasm(WasmError::StackPointerNotFound))?;
let mut offset = stack_pointer
.get(&mut store)
.i32()
.ok_or(Error::Wasm(WasmError::ValueTypeMismatch))?;

let memory = instance
.get_memory(&mut store, "memory")
.ok_or(Error::Wasm(WasmError::MemoryNotFound))?;

// Determine how much space is needed for arguments
let mut arg_size = 0;
for arg in func_types.get_arg_types() {
arg_size += get_type_size(arg);
}
let mut in_mem_offset = offset + arg_size;

// Convert the args into wasmtime values
let mut wasm_args = vec![];
for arg in args {
let (arg_vec, new_offset, new_in_mem_offset) =
pass_argument_to_wasm(memory, &mut store, arg, offset, in_mem_offset)?;
wasm_args.extend(arg_vec);
offset = new_offset;
in_mem_offset = new_in_mem_offset;
}

// Reserve stack space for the return value, if necessary.
let return_type = store
.data()
.contract_context()
.functions
.get(function_name)
.ok_or(CheckErrors::UndefinedFunction(function_name.to_string()))?
.get_return_type()
.as_ref()
.ok_or(Error::Wasm(WasmError::ExpectedReturnValue))?
.clone();
let (mut results, offset) = reserve_space_for_return(&mut store, offset, &return_type)?;

// Update the stack pointer after space is reserved for the arguments and
// return values.
stack_pointer
.set(&mut store, Val::I32(offset))
.map_err(|e| Error::Wasm(WasmError::Runtime(e)))?;

// Call the function
func.call(&mut store, &wasm_args, &mut results)
.map_err(|e| {
// TODO: If the root cause is a clarity error, we should be able to return that,
// but it is not cloneable, so we can't return it directly.
// If the root cause is a trap from our Wasm code, then we need to translate
// it into a Clarity error.
// See issue stacks-network/clarity-wasm#104
// if let Some(vm_error) = e.root_cause().downcast_ref::<crate::vm::errors::Error>() {
// vm_error.clone()
// } else {
// Error::Wasm(WasmError::Runtime(e))
// }
Error::Wasm(WasmError::Runtime(e))
})?;

// If the function returns a value, translate it into a Clarity `Value`
wasm_to_clarity_value(&return_type, 0, &results, memory, &mut &mut store, epoch)
.map(|(val, _offset)| val)
.and_then(|option_value| {
option_value.ok_or_else(|| Error::Wasm(WasmError::ExpectedReturnValue))
})
}

/// Convert a Clarity `Value` into one or more Wasm `Val`. If this value
/// requires writing into the Wasm memory, write it to the provided `offset`.
/// Return a vector of `Val`s that can be passed to a Wasm function, and the
/// two offsets, adjusted to the next available memory location.
fn pass_argument_to_wasm(
memory: Memory,
mut store: impl AsContextMut,
value: &Value,
offset: i32,
in_mem_offset: i32,
) -> Result<(Vec<Val>, i32, i32), Error> {
match value {
Value::UInt(n) => {
let high = (n >> 64) as u64;
let low = (n & 0xffff_ffff_ffff_ffff) as u64;
let buffer = vec![Val::I64(low as i64), Val::I64(high as i64)];
Ok((buffer, offset, in_mem_offset))
}
Value::Int(n) => {
let high = (n >> 64) as u64;
let low = (n & 0xffff_ffff_ffff_ffff) as u64;
let buffer = vec![Val::I64(low as i64), Val::I64(high as i64)];
Ok((buffer, offset, in_mem_offset))
}
Value::Bool(b) => Ok((
vec![Val::I32(if *b { 1 } else { 0 })],
offset,
in_mem_offset,
)),
Value::Optional(o) => {
let mut buffer = vec![Val::I32(if o.data.is_some() { 1 } else { 0 })];
let (inner, new_offset, new_in_mem_offset) = pass_argument_to_wasm(
memory,
store,
o.data
.as_ref()
.map_or(&Value::none(), |boxed_value| &boxed_value),
offset,
in_mem_offset,
)?;
buffer.extend(inner);
Ok((buffer, new_offset, new_in_mem_offset))
}
Value::Response(r) => {
let mut buffer = vec![Val::I32(if r.committed { 1 } else { 0 })];
let (inner, new_offset, new_in_mem_offset) = if r.committed {
pass_argument_to_wasm(memory, store, &r.data, offset, in_mem_offset)?
} else {
pass_argument_to_wasm(memory, store, &r.data, offset, in_mem_offset)?
};
buffer.extend(inner);
Ok((buffer, new_offset, new_in_mem_offset))
}
Value::Sequence(SequenceData::String(CharType::ASCII(s))) => {
// For a string, write the bytes into the memory, then pass the
// offset and length to the Wasm function.
let buffer = vec![Val::I32(in_mem_offset), Val::I32(s.data.len() as i32)];
memory
.write(
store.borrow_mut(),
in_mem_offset as usize,
s.data.as_slice(),
)
.map_err(|e| Error::Wasm(WasmError::UnableToWriteMemory(e.into())))?;
let adjusted_in_mem_offset = in_mem_offset + s.data.len() as i32;
Ok((buffer, offset, adjusted_in_mem_offset))
}
Value::Sequence(SequenceData::String(CharType::UTF8(_s))) => {
todo!("Value type not yet implemented: {:?}", value)
}
Value::Sequence(SequenceData::Buffer(b)) => {
// For a buffer, write the bytes into the memory, then pass the
// offset and length to the Wasm function.
let buffer = vec![Val::I32(in_mem_offset), Val::I32(b.data.len() as i32)];
memory
.write(
store.borrow_mut(),
in_mem_offset as usize,
b.data.as_slice(),
)
.map_err(|e| Error::Wasm(WasmError::UnableToWriteMemory(e.into())))?;
let adjusted_in_mem_offset = in_mem_offset + b.data.len() as i32;
Ok((buffer, offset, adjusted_in_mem_offset))
}
Value::Sequence(SequenceData::List(l)) => {
let mut buffer = vec![Val::I32(offset)];
let mut written = 0;
let mut in_mem_written = 0;
for item in &l.data {
let (len, in_mem_len) = write_to_wasm(
&mut store,
memory,
l.type_signature.get_list_item_type(),
offset + written,
in_mem_offset + in_mem_written,
item,
true,
)?;
written += len;
in_mem_written += in_mem_len;
}
buffer.push(Val::I32(written));
Ok((buffer, offset + written, in_mem_offset + in_mem_written))
}
Value::Principal(_p) => todo!("Value type not yet implemented: {:?}", value),
Value::CallableContract(_c) => todo!("Value type not yet implemented: {:?}", value),
Value::Tuple(_t) => todo!("Value type not yet implemented: {:?}", value),
}
}

/// Reserve space on the Wasm stack for the return value of a function, if
/// needed, and return a vector of `Val`s that can be passed to `call`, as a
/// place to store the return value, along with the new offset, which is the
/// next available memory location.
fn reserve_space_for_return<T>(
store: &mut Store<T>,
offset: i32,
return_type: &TypeSignature,
) -> Result<(Vec<Val>, i32), Error> {
match return_type {
TypeSignature::UIntType | TypeSignature::IntType => {
Ok((vec![Val::I64(0), Val::I64(0)], offset))
}
TypeSignature::BoolType => Ok((vec![Val::I32(0)], offset)),
TypeSignature::OptionalType(optional) => {
let mut vals = vec![Val::I32(0)];
let (opt_vals, adjusted) = reserve_space_for_return(store, offset, optional)?;
vals.extend(opt_vals);
Ok((vals, adjusted))
}
TypeSignature::ResponseType(response) => {
let mut vals = vec![Val::I32(0)];
let (mut subexpr_values, mut adjusted) =
reserve_space_for_return(store, offset, &response.0)?;
vals.extend(subexpr_values);
(subexpr_values, adjusted) = reserve_space_for_return(store, adjusted, &response.1)?;
vals.extend(subexpr_values);
Ok((vals, adjusted))
}
TypeSignature::NoType => Ok((vec![Val::I32(0)], offset)),
TypeSignature::SequenceType(_)
| TypeSignature::PrincipalType
| TypeSignature::CallableType(_)
| TypeSignature::TraitReferenceType(_) => {
// All in-memory types return an offset and length.˝
let length = get_type_in_memory_size(return_type, false);

// Return values will be offset and length
Ok((vec![Val::I32(0), Val::I32(0)], offset + length))
}
TypeSignature::TupleType(type_sig) => {
let mut vals = vec![];
let mut adjusted = offset;
for ty in type_sig.get_type_map().values() {
let (subexpr_values, new_offset) = reserve_space_for_return(store, adjusted, ty)?;
vals.extend(subexpr_values);
adjusted = new_offset;
}
Ok((vals, adjusted))
}
TypeSignature::ListUnionType(_) => {
unreachable!("not a valid return type");
}
}
}
Loading

0 comments on commit 363b9be

Please sign in to comment.