linera_sdk/contract/
mod.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Types and macros useful for writing an application contract.
5
6mod conversions_from_wit;
7mod conversions_to_wit;
8#[cfg(not(with_testing))]
9mod runtime;
10#[cfg(with_testing)]
11mod test_runtime;
12#[doc(hidden)]
13pub mod wit;
14
15#[cfg(not(with_testing))]
16pub use self::runtime::ContractRuntime;
17#[cfg(with_testing)]
18pub use self::test_runtime::MockContractRuntime;
19#[doc(hidden)]
20pub use self::wit::export_contract;
21use crate::{log::ContractLogger, util::BlockingWait};
22
23/// Inside tests, use the [`MockContractRuntime`] instead of the real [`ContractRuntime`].
24#[cfg(with_testing)]
25pub type ContractRuntime<Application> = MockContractRuntime<Application>;
26
27/// Declares an implementation of the [`Contract`][`crate::Contract`] trait, exporting it from the
28/// Wasm module.
29///
30/// Generates the necessary boilerplate for implementing the contract WIT interface, exporting the
31/// necessary resource types and functions so that the host can call the application contract.
32#[macro_export]
33macro_rules! contract {
34    ($contract:ident) => {
35        #[doc(hidden)]
36        static mut CONTRACT: Option<$contract> = None;
37
38        /// Export the contract interface.
39        $crate::export_contract!($contract with_types_in $crate::contract::wit);
40
41        /// Mark the contract type to be exported.
42        impl $crate::contract::wit::exports::linera::app::contract_entrypoints::Guest
43            for $contract
44        {
45            fn instantiate(argument: Vec<u8>) {
46                use $crate::util::BlockingWait;
47                $crate::contract::run_async_entrypoint::<$contract, _, _>(
48                    unsafe { &mut CONTRACT },
49                    move |contract| {
50                        let argument = $crate::serde_json::from_slice(&argument)
51                            .unwrap_or_else(|_| panic!("Failed to deserialize instantiation argument {argument:?}"));
52
53                        contract.instantiate(argument).blocking_wait()
54                    },
55                )
56            }
57
58            fn execute_operation(operation: Vec<u8>) -> Vec<u8> {
59                use $crate::util::BlockingWait;
60                $crate::contract::run_async_entrypoint::<$contract, _, _>(
61                    unsafe { &mut CONTRACT },
62                    move |contract| {
63                        let operation = <$contract as $crate::abi::ContractAbi>::deserialize_operation(operation)
64                            .expect("Failed to deserialize `Operation` in execute_operation");
65
66                        let response = contract.execute_operation(operation).blocking_wait();
67
68                        <$contract as $crate::abi::ContractAbi>::serialize_response(response)
69                            .expect("Failed to serialize `Response` in execute_operation")
70                    },
71                )
72            }
73
74            fn execute_message(message: Vec<u8>) {
75                use $crate::util::BlockingWait;
76                $crate::contract::run_async_entrypoint::<$contract, _, _>(
77                    unsafe { &mut CONTRACT },
78                    move |contract| {
79                        let message: <$contract as $crate::Contract>::Message =
80                            $crate::bcs::from_bytes(&message)
81                                .expect("Failed to deserialize message");
82
83                        contract.execute_message(message).blocking_wait()
84                    },
85                )
86            }
87
88            fn process_streams(updates: Vec<
89                $crate::contract::wit::exports::linera::app::contract_entrypoints::StreamUpdate,
90            >) {
91                use $crate::util::BlockingWait;
92                $crate::contract::run_async_entrypoint::<$contract, _, _>(
93                    unsafe { &mut CONTRACT },
94                    move |contract| {
95                        let updates = updates.into_iter().map(Into::into).collect();
96                        contract.process_streams(updates).blocking_wait()
97                    },
98                )
99            }
100
101            fn finalize() {
102                use $crate::util::BlockingWait;
103
104                let contract = unsafe { CONTRACT.take() }
105                    .expect("Calling `store` on a `Contract` instance that wasn't loaded");
106
107                contract.store().blocking_wait();
108            }
109        }
110
111        /// Stub of a `main` entrypoint so that the binary doesn't fail to compile on targets other
112        /// than WebAssembly.
113        #[cfg(not(target_arch = "wasm32"))]
114        fn main() {}
115    };
116}
117
118/// Runs an asynchronous entrypoint in a blocking manner, by repeatedly polling the entrypoint
119/// future.
120pub fn run_async_entrypoint<Contract, Output, RawOutput>(
121    contract: &mut Option<Contract>,
122    entrypoint: impl FnOnce(&mut Contract) -> Output + Send,
123) -> RawOutput
124where
125    Contract: crate::Contract,
126    Output: Into<RawOutput> + Send + 'static,
127{
128    ContractLogger::install();
129
130    let contract =
131        contract.get_or_insert_with(|| Contract::load(ContractRuntime::new()).blocking_wait());
132
133    entrypoint(contract).into()
134}