calimero_runtime/logic/host_functions/governance.rs
1use rand::RngCore;
2
3use crate::logic::{sys, VMHostFunctions, VMLogicResult, DIGEST_SIZE};
4
5impl VMHostFunctions<'_> {
6 /// Creates a new governance proposal.
7 ///
8 /// Call the contract's `send_proposal()` function through the bridge.
9 ///
10 /// The proposal actions are obtained as raw data and pushed onto a list of
11 /// proposals to be sent to the host.
12 ///
13 /// Note that multiple actions are received, and the entire batch is pushed
14 /// onto the proposal list to represent one proposal.
15 ///
16 /// A unique ID for the proposal is generated by the host and written back into
17 /// guest memory. The proposal itself is stored in the `VMLogic` to be included
18 /// in the `Outcome`.
19 ///
20 /// # Arguments
21 ///
22 /// * `src_actions_ptr` - pointer to a source-buffer `sys::Buffer` in guest memory,
23 /// containing the proposal's actions.
24 /// * `dest_id_ptr` - A pointer to a 32-byte destination buffer `sys::BufferMut`
25 /// in guest memory where the generated proposal ID will be written.
26 ///
27 /// # Errors
28 ///
29 /// * `HostError::InvalidMemoryAccess` if memory access fails for descriptor buffers.
30 pub fn send_proposal(&mut self, src_actions_ptr: u64, dest_id_ptr: u64) -> VMLogicResult<()> {
31 let actions = unsafe { self.read_guest_memory_typed::<sys::Buffer<'_>>(src_actions_ptr)? };
32 let dest_id = unsafe { self.read_guest_memory_typed::<sys::BufferMut<'_>>(dest_id_ptr)? };
33
34 let mut proposal_id = [0u8; DIGEST_SIZE];
35 rand::thread_rng().fill_bytes(&mut proposal_id);
36
37 // Record newly created ID to guest memory
38 let dest_id: &mut [u8] = self.read_guest_memory_slice_mut(&dest_id);
39 dest_id.copy_from_slice(&proposal_id);
40
41 let actions = self.read_guest_memory_slice(&actions).to_vec();
42
43 let _ignored = self.with_logic_mut(|logic| logic.proposals.insert(proposal_id, actions));
44 Ok(())
45 }
46
47 /// Approves a governance proposal.
48 ///
49 /// Adds the given proposal ID to the list of approvals in the `VMLogic`.
50 ///
51 /// # Arguments
52 ///
53 /// * `src_approval_ptr` - Pointer to a 32-byte source-buffer `sys::Buffer`
54 /// in guest memory containing the ID of the proposal to approve.
55 ///
56 /// # Errors
57 ///
58 /// * `HostError::InvalidMemoryAccess` if memory access fails for descriptor buffers.
59 pub fn approve_proposal(&mut self, src_approval_ptr: u64) -> VMLogicResult<()> {
60 let approval =
61 unsafe { self.read_guest_memory_typed::<sys::Buffer<'_>>(src_approval_ptr)? };
62 let approval = *self.read_guest_memory_sized::<DIGEST_SIZE>(&approval)?;
63
64 let _ignored = self.with_logic_mut(|logic| logic.approvals.push(approval));
65 Ok(())
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use crate::logic::{
72 tests::{prepare_guest_buf_descriptor, setup_vm, SimpleMockStorage},
73 Cow, VMContext, VMLimits, VMLogic, DIGEST_SIZE,
74 };
75 use wasmer::{AsStoreMut, Store};
76
77 /// Tests the `send_proposal()` and `approve_proposal()` host functions.
78 #[test]
79 fn test_proposals_send_approve() {
80 let mut storage = SimpleMockStorage::new();
81 let limits = VMLimits::default();
82 let (mut logic, mut store) = setup_vm!(&mut storage, &limits, vec![]);
83 let mut host = logic.host_functions(store.as_store_mut());
84
85 // Test sending a proposal.
86 let actions = vec![1, 2, 3, 4, 5, 6];
87 let actions_ptr = 100u64;
88 // Write actions to guest memory.
89 host.borrow_memory().write(actions_ptr, &actions).unwrap();
90 let actions_buf_ptr = 16u64;
91 // Guest: prepare the descriptor for the destination buffer so host can access it.
92 prepare_guest_buf_descriptor(&host, actions_buf_ptr, actions_ptr, actions.len() as u64);
93
94 let id_out_ptr = 300u64;
95 let id_buf_ptr = 32u64;
96 // Guest: prepare the descriptor for the destination buffer so host can write there.
97 prepare_guest_buf_descriptor(&host, id_buf_ptr, id_out_ptr, DIGEST_SIZE as u64);
98 // Guest: send proposal to host with actions `actions_buf_ptr` and get back the proposal ID
99 // in `id_buf_ptr`.
100 host.send_proposal(actions_buf_ptr, id_buf_ptr).unwrap();
101
102 // Verify the proposal with the given actions were successfully added.
103 assert_eq!(host.borrow_logic().proposals.len(), 1);
104 assert_eq!(
105 host.borrow_logic().proposals.values().next().unwrap(),
106 &actions
107 );
108 // Verify there are no approvals yet.
109 assert_eq!(host.borrow_logic().approvals.len(), 0);
110
111 // Test approving a proposal.
112 // Approval ID is the Answer to the Ultimate Question of Life, the Universe, and Everything.
113 let approval_id = [42u8; DIGEST_SIZE];
114 let approval_ptr = 500u64;
115 // Write approval to guest memory.
116 host.borrow_memory()
117 .write(approval_ptr, &approval_id)
118 .unwrap();
119
120 let approval_buf_ptr = 48u64;
121 // Guest: prepare the descriptor for the destination buffer so host can access it.
122 prepare_guest_buf_descriptor(
123 &host,
124 approval_buf_ptr,
125 approval_ptr,
126 approval_id.len() as u64,
127 );
128
129 // Guest: send a proposal approval to host.
130 host.approve_proposal(approval_buf_ptr).unwrap();
131
132 // Verify the host successfully stored the approval and its ID matches the one we sent.
133 assert_eq!(host.borrow_logic().approvals.len(), 1);
134 assert_eq!(host.borrow_logic().approvals[0], approval_id);
135 }
136}