Skip to content

Commit

Permalink
feat: Blockchain WASM integration (#899)
Browse files Browse the repository at this point in the history
  • Loading branch information
danwilliams authored Nov 2, 2024
1 parent cf9eadd commit c291850
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 2 deletions.
43 changes: 43 additions & 0 deletions crates/runtime/src/logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![allow(clippy::mem_forget, reason = "Safe for now")]

use core::num::NonZeroU64;
use std::collections::BTreeMap;
use std::time::{SystemTime, UNIX_EPOCH};

use borsh::from_slice as from_borsh_slice;
Expand Down Expand Up @@ -108,6 +109,7 @@ pub struct VMLogic<'a> {
events: Vec<Event>,
actions: Vec<Vec<u8>>,
root_hash: Option<[u8; 32]>,
proposals: BTreeMap<[u8; 32], Vec<u8>>,
}

impl<'a> VMLogic<'a> {
Expand All @@ -123,6 +125,7 @@ impl<'a> VMLogic<'a> {
events: vec![],
actions: vec![],
root_hash: None,
proposals: BTreeMap::new(),
}
}

Expand Down Expand Up @@ -152,6 +155,7 @@ pub struct Outcome {
pub events: Vec<Event>,
pub actions: Vec<Vec<u8>>,
pub root_hash: Option<[u8; 32]>,
pub proposals: BTreeMap<[u8; 32], Vec<u8>>,
// execution runtime
// current storage usage of the app
}
Expand Down Expand Up @@ -181,6 +185,7 @@ impl VMLogic<'_> {
events: self.events,
actions: self.actions,
root_hash: self.root_hash,
proposals: self.proposals,
}
}
}
Expand Down Expand Up @@ -556,4 +561,42 @@ impl VMHostFunctions<'_> {

Ok(())
}

/// Call the contract's `send_proposal()` function through the bridge.
///
/// The proposal actions are obtained as raw data and pushed onto a list of
/// proposals to be sent to the host.
///
/// Note that multiple actions are received, and the entire batch is pushed
/// onto the proposal list to represent one proposal.
///
/// # Parameters
///
/// * `actions_ptr` - Pointer to the start of the action data in WASM
/// memory.
/// * `actions_len` - Length of the action data.
/// * `id_ptr` - Pointer to the start of the id data in WASM memory.
/// * `id_len` - Length of the action data. This should be 32 bytes.
///
pub fn send_proposal(
&mut self,
actions_ptr: u64,
actions_len: u64,
id_ptr: u64,
id_len: u64,
) -> VMLogicResult<()> {
if id_len != 32 {
return Err(HostError::InvalidMemoryAccess.into());
}

let actions_bytes: Vec<u8> = self.read_guest_memory(actions_ptr, actions_len)?;
let mut proposal_id = [0; 32];

rand::thread_rng().fill_bytes(&mut proposal_id);
drop(self.with_logic_mut(|logic| logic.proposals.insert(proposal_id, actions_bytes)));

self.borrow_memory().write(id_ptr, &proposal_id)?;

Ok(())
}
}
2 changes: 2 additions & 0 deletions crates/runtime/src/logic/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ impl VMLogic<'_> {

fn random_bytes(ptr: u64, len: u64);
fn time_now(ptr: u64, len: u64);

fn send_proposal(actions_ptr: u64, actions_len: u64, id_ptr: u64, id_len: u64);
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions crates/sdk/macros/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ impl ToTokens for StateImpl<'_> {
impl #impl_generics ::calimero_sdk::state::AppState for #ident #ty_generics #where_clause {
type Event<#lifetime> = #event;
}

impl #impl_generics #ident #ty_generics #where_clause {
fn external() -> ::calimero_sdk::env::ext::External {
::calimero_sdk::env::ext::External {}
}
}
}
.to_tokens(tokens);
}
Expand Down
129 changes: 127 additions & 2 deletions crates/sdk/src/env/ext.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,133 @@
use borsh::to_vec as to_borsh_vec;
use borsh::{to_vec as to_borsh_vec, to_vec, BorshDeserialize, BorshSerialize};

use super::{expected_boolean, expected_register, panic_str, read_register, DATA_REGISTER};
use crate::sys;
use crate::sys::Buffer;
use crate::sys::{Buffer, BufferMut};

/// A blockchain proposal action.
///
/// This enum represents the different actions that can be executed against a
/// blockchain, and combined into a proposal.
///
#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, PartialEq)]
pub enum ProposalAction {
/// Call a method on a contract.
ExternalFunctionCall {
/// The account ID of the contract to call.
receiver_id: AccountId,

/// The method name to call.
method_name: String,

/// The arguments to pass to the method.
args: String,

/// The amount of tokens to attach to the call.
deposit: u64,

/// The maximum amount of gas to use for the call.
gas: u64,
},

/// Transfer tokens to an account.
Transfer {
/// The account ID of the receiver.
receiver_id: AccountId,

/// The amount of tokens to transfer.
amount: u64,
},

/// Set the number of approvals required for a proposal to be executed.
SetNumApprovals {
/// The number of approvals required.
num_approvals: u32,
},

/// Set the number of active proposals allowed at once.
SetActiveProposalsLimit {
/// The number of active proposals allowed.
active_proposals_limit: u32,
},

/// Set a value in the contract's context.
SetContextValue {
/// The key to set.
key: Box<[u8]>,

/// The value to set.
value: Box<[u8]>,
},
}

/// Unique identifier for an account.
#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct AccountId(String);

/// A draft proposal.
///
/// This struct is used to build a proposal before sending it to the blockchain.
/// It is distinct from a proposal that has been prepared and needs signing.
///
#[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Default, Eq, PartialEq)]
pub struct DraftProposal {
/// The actions to be executed by the proposal. One proposal can contain
/// multiple actions to execute.
actions: Vec<ProposalAction>,
}

impl DraftProposal {
/// Create a new draft proposal.
#[must_use]
pub const fn new() -> Self {
Self {
actions: Vec::new(),
}
}

/// Add an action to transfer tokens to an account.
#[must_use]
pub fn transfer(mut self, receiver: AccountId, amount: u64) -> Self {
self.actions.push(ProposalAction::Transfer {
receiver_id: receiver,
amount,
});
self
}

/// Finalise the proposal and send it to the blockchain.
#[must_use]
pub fn send(self) -> ProposalId {
let mut buf = [0; 32];
let actions = to_vec(&self.actions).unwrap();

#[expect(
clippy::needless_borrows_for_generic_args,
reason = "We don't want to copy the buffer, but write to the same one that's returned"
)]
unsafe {
sys::send_proposal(Buffer::from(&*actions), BufferMut::new(&mut buf))
}

ProposalId(buf)
}
}

/// Interface for interacting with external proposals for blockchain actions.
#[derive(Clone, Copy, Debug)]
pub struct External;

impl External {
/// Create a new proposal. This will initially be a draft, until sent.
#[must_use]
pub const fn propose(self) -> DraftProposal {
DraftProposal::new()
}
}

/// Unique identifier for a proposal.
#[derive(BorshDeserialize, BorshSerialize, Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct ProposalId(pub [u8; 32]);

#[doc(hidden)]
pub unsafe fn fetch(
Expand Down
2 changes: 2 additions & 0 deletions crates/sdk/src/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ wasm_imports! {
// --
fn random_bytes(buf: BufferMut<'_>);
fn time_now(buf: BufferMut<'_>);
// --
fn send_proposal(value: Buffer<'_>, buf: BufferMut<'_>);
}
}

Expand Down

0 comments on commit c291850

Please sign in to comment.