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}