linera_sdk/contract/
runtime.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Runtime types to interface with the host executing the contract.
5
6use linera_base::{
7    abi::{ContractAbi, ServiceAbi},
8    data_types::{
9        Amount, ApplicationPermissions, BlockHeight, Bytecode, Resources, SendMessageRequest,
10        Timestamp,
11    },
12    ensure, http,
13    identifiers::{
14        Account, AccountOwner, ApplicationId, ChainId, DataBlobHash, ModuleId, StreamName,
15    },
16    ownership::{
17        AccountPermissionError, ChainOwnership, ChangeApplicationPermissionsError, CloseChainError,
18    },
19    vm::VmRuntime,
20};
21use serde::Serialize;
22
23use super::wit::{base_runtime_api as base_wit, contract_runtime_api as contract_wit};
24use crate::{Contract, KeyValueStore, ViewStorageContext};
25
26/// The common runtime to interface with the host executing the contract.
27///
28/// It automatically caches read-only values received from the host.
29#[derive(Debug)]
30pub struct ContractRuntime<Application>
31where
32    Application: Contract,
33{
34    application_parameters: Option<Application::Parameters>,
35    application_id: Option<ApplicationId<Application::Abi>>,
36    application_creator_chain_id: Option<ChainId>,
37    chain_id: Option<ChainId>,
38    block_height: Option<BlockHeight>,
39    message_is_bouncing: Option<Option<bool>>,
40    message_origin_chain_id: Option<Option<ChainId>>,
41    timestamp: Option<Timestamp>,
42}
43
44impl<Application> ContractRuntime<Application>
45where
46    Application: Contract,
47{
48    /// Creates a new [`ContractRuntime`] instance for a contract.
49    pub(crate) fn new() -> Self {
50        ContractRuntime {
51            application_parameters: None,
52            application_id: None,
53            application_creator_chain_id: None,
54            chain_id: None,
55            block_height: None,
56            message_is_bouncing: None,
57            message_origin_chain_id: None,
58            timestamp: None,
59        }
60    }
61
62    /// Returns the key-value store to interface with storage.
63    pub fn key_value_store(&self) -> KeyValueStore {
64        KeyValueStore::for_contracts()
65    }
66
67    /// Returns a storage context suitable for a root view.
68    pub fn root_view_storage_context(&self) -> ViewStorageContext {
69        ViewStorageContext::new_unchecked(self.key_value_store(), Vec::new(), ())
70    }
71}
72
73impl<Application> ContractRuntime<Application>
74where
75    Application: Contract,
76{
77    /// Returns the application parameters provided when the application was created.
78    pub fn application_parameters(&mut self) -> Application::Parameters {
79        self.application_parameters
80            .get_or_insert_with(|| {
81                let bytes = base_wit::application_parameters();
82                serde_json::from_slice(&bytes)
83                    .expect("Application parameters must be deserializable")
84            })
85            .clone()
86    }
87
88    /// Returns the ID of the current application.
89    pub fn application_id(&mut self) -> ApplicationId<Application::Abi> {
90        *self
91            .application_id
92            .get_or_insert_with(|| ApplicationId::from(base_wit::get_application_id()).with_abi())
93    }
94
95    /// Returns the chain ID of the current application creator.
96    pub fn application_creator_chain_id(&mut self) -> ChainId {
97        *self
98            .application_creator_chain_id
99            .get_or_insert_with(|| base_wit::get_application_creator_chain_id().into())
100    }
101
102    /// Returns the ID of the current chain.
103    pub fn chain_id(&mut self) -> ChainId {
104        *self
105            .chain_id
106            .get_or_insert_with(|| base_wit::get_chain_id().into())
107    }
108
109    /// Returns the height of the current block that is executing.
110    pub fn block_height(&mut self) -> BlockHeight {
111        *self
112            .block_height
113            .get_or_insert_with(|| base_wit::get_block_height().into())
114    }
115
116    /// Retrieves the current system time, i.e. the timestamp of the block in which this is called.
117    pub fn system_time(&mut self) -> Timestamp {
118        *self
119            .timestamp
120            .get_or_insert_with(|| base_wit::read_system_timestamp().into())
121    }
122
123    /// Returns the current chain balance.
124    pub fn chain_balance(&mut self) -> Amount {
125        base_wit::read_chain_balance().into()
126    }
127
128    /// Returns the balance of one of the accounts on this chain.
129    pub fn owner_balance(&mut self, owner: AccountOwner) -> Amount {
130        base_wit::read_owner_balance(owner.into()).into()
131    }
132
133    /// Retrieves the owner configuration for the current chain.
134    pub fn chain_ownership(&mut self) -> ChainOwnership {
135        base_wit::get_chain_ownership().into()
136    }
137
138    /// Makes an HTTP `request` as an oracle and returns the HTTP response.
139    ///
140    /// Should only be used with queries where it is very likely that all validators will receive
141    /// the same response, otherwise most block proposals will fail.
142    ///
143    /// Cannot be used in fast blocks: A block using this call should be proposed by a regular
144    /// owner, not a super owner.
145    pub fn http_request(&mut self, request: http::Request) -> http::Response {
146        base_wit::perform_http_request(&request.into()).into()
147    }
148
149    /// Panics if the current time at block validation is `>= timestamp`. Note that block
150    /// validation happens at or after the block timestamp, but isn't necessarily the same.
151    ///
152    /// Cannot be used in fast blocks: A block using this call should be proposed by a regular
153    /// owner, not a super owner.
154    pub fn assert_before(&mut self, timestamp: Timestamp) {
155        base_wit::assert_before(timestamp.into());
156    }
157
158    /// Reads a data blob with the given hash from storage.
159    pub fn read_data_blob(&mut self, hash: DataBlobHash) -> Vec<u8> {
160        base_wit::read_data_blob(hash.into())
161    }
162
163    /// Asserts that a data blob with the given hash exists in storage.
164    pub fn assert_data_blob_exists(&mut self, hash: DataBlobHash) {
165        base_wit::assert_data_blob_exists(hash.into())
166    }
167}
168
169impl<Application> ContractRuntime<Application>
170where
171    Application: Contract,
172{
173    /// Returns the authenticated signer for this execution, if there is one.
174    pub fn authenticated_signer(&mut self) -> Option<AccountOwner> {
175        contract_wit::authenticated_signer().map(AccountOwner::from)
176    }
177
178    /// Returns [`true`] if the incoming message was rejected from the original destination and is
179    /// now bouncing back, or [`None`] if not executing an incoming message.
180    pub fn message_is_bouncing(&mut self) -> Option<bool> {
181        *self
182            .message_is_bouncing
183            .get_or_insert_with(contract_wit::message_is_bouncing)
184    }
185
186    /// Returns the chain ID where the incoming message originated from, or [`None`] if not executing
187    /// an incoming message.
188    pub fn message_origin_chain_id(&mut self) -> Option<ChainId> {
189        *self
190            .message_origin_chain_id
191            .get_or_insert_with(|| contract_wit::message_origin_chain_id().map(ChainId::from))
192    }
193
194    /// Returns the authenticated caller ID, if the caller configured it and if the current context
195    /// is executing a cross-application call.
196    pub fn authenticated_caller_id(&mut self) -> Option<ApplicationId> {
197        contract_wit::authenticated_caller_id().map(ApplicationId::from)
198    }
199
200    /// Verifies that the current execution context authorizes operations on a given account.
201    pub fn check_account_permission(
202        &mut self,
203        owner: AccountOwner,
204    ) -> Result<(), AccountPermissionError> {
205        ensure!(
206            self.authenticated_signer() == Some(owner)
207                || self.authenticated_caller_id().map(AccountOwner::from) == Some(owner),
208            AccountPermissionError::NotPermitted(owner)
209        );
210        Ok(())
211    }
212
213    /// Schedules a message to be sent to this application on another chain.
214    pub fn send_message(&mut self, destination: ChainId, message: Application::Message) {
215        self.prepare_message(message).send_to(destination)
216    }
217
218    /// Returns a `MessageBuilder` to prepare a message to be sent.
219    pub fn prepare_message(
220        &mut self,
221        message: Application::Message,
222    ) -> MessageBuilder<Application::Message> {
223        MessageBuilder::new(message)
224    }
225
226    /// Transfers an `amount` of native tokens from `source` owner account (or the current chain's
227    /// balance) to `destination`.
228    pub fn transfer(&mut self, source: AccountOwner, destination: Account, amount: Amount) {
229        contract_wit::transfer(source.into(), destination.into(), amount.into())
230    }
231
232    /// Claims an `amount` of native tokens from a `source` account to a `destination` account.
233    pub fn claim(&mut self, source: Account, destination: Account, amount: Amount) {
234        contract_wit::claim(source.into(), destination.into(), amount.into())
235    }
236
237    /// Calls another application.
238    // ANCHOR: call_application
239    pub fn call_application<A: ContractAbi + Send>(
240        &mut self,
241        authenticated: bool,
242        application: ApplicationId<A>,
243        call: &A::Operation,
244    ) -> A::Response
245// ANCHOR_END: call_application
246    {
247        let call_bytes = A::serialize_operation(call)
248            .expect("Failed to serialize `Operation` in cross-application call");
249
250        let response_bytes = contract_wit::try_call_application(
251            authenticated,
252            application.forget_abi().into(),
253            &call_bytes,
254        );
255
256        A::deserialize_response(response_bytes)
257            .expect("Failed to deserialize `Response` in cross-application call")
258    }
259
260    /// Adds a new item to an event stream. Returns the new event's index in the stream.
261    pub fn emit(&mut self, name: StreamName, value: &Application::EventValue) -> u32 {
262        contract_wit::emit(
263            &name.into(),
264            &bcs::to_bytes(value).expect("Failed to serialize event"),
265        )
266    }
267
268    /// Reads an event from a stream. Returns the event's value.
269    ///
270    /// Fails the block if the event doesn't exist.
271    pub fn read_event(
272        &mut self,
273        chain_id: ChainId,
274        name: StreamName,
275        index: u32,
276    ) -> Application::EventValue {
277        let event = contract_wit::read_event(chain_id.into(), &name.into(), index);
278        bcs::from_bytes(&event).expect("Failed to deserialize event")
279    }
280
281    /// Subscribes this application to an event stream.
282    pub fn subscribe_to_events(
283        &mut self,
284        chain_id: ChainId,
285        application_id: ApplicationId,
286        name: StreamName,
287    ) {
288        contract_wit::subscribe_to_events(chain_id.into(), application_id.into(), &name.into())
289    }
290
291    /// Unsubscribes this application from an event stream.
292    pub fn unsubscribe_from_events(
293        &mut self,
294        chain_id: ChainId,
295        application_id: ApplicationId,
296        name: StreamName,
297    ) {
298        contract_wit::unsubscribe_from_events(chain_id.into(), application_id.into(), &name.into())
299    }
300
301    /// Queries an application service as an oracle and returns the response.
302    ///
303    /// Should only be used with queries where it is very likely that all validators will compute
304    /// the same result, otherwise most block proposals will fail.
305    ///
306    /// Cannot be used in fast blocks: A block using this call should be proposed by a regular
307    /// owner, not a super owner.
308    pub fn query_service<A: ServiceAbi + Send>(
309        &mut self,
310        application_id: ApplicationId<A>,
311        query: A::Query,
312    ) -> A::QueryResponse {
313        let query = serde_json::to_vec(&query).expect("Failed to serialize service query");
314        let response = contract_wit::query_service(application_id.forget_abi().into(), &query);
315        serde_json::from_slice(&response).expect("Failed to deserialize service response")
316    }
317
318    /// Opens a new chain, configuring it with the provided `chain_ownership`,
319    /// `application_permissions` and initial `balance` (debited from the current chain).
320    pub fn open_chain(
321        &mut self,
322        chain_ownership: ChainOwnership,
323        application_permissions: ApplicationPermissions,
324        balance: Amount,
325    ) -> ChainId {
326        let chain_id = contract_wit::open_chain(
327            &chain_ownership.into(),
328            &application_permissions.into(),
329            balance.into(),
330        );
331        chain_id.into()
332    }
333
334    /// Closes the current chain. Returns an error if the application doesn't have
335    /// permission to do so.
336    pub fn close_chain(&mut self) -> Result<(), CloseChainError> {
337        contract_wit::close_chain().map_err(|error| error.into())
338    }
339
340    /// Changes the application permissions for the current chain.
341    pub fn change_application_permissions(
342        &mut self,
343        application_permissions: ApplicationPermissions,
344    ) -> Result<(), ChangeApplicationPermissionsError> {
345        contract_wit::change_application_permissions(&application_permissions.into())
346            .map_err(|error| error.into())
347    }
348
349    /// Creates a new on-chain application, based on the supplied module and parameters.
350    pub fn create_application<Abi, Parameters, InstantiationArgument>(
351        &mut self,
352        module_id: ModuleId,
353        parameters: &Parameters,
354        argument: &InstantiationArgument,
355        required_application_ids: Vec<ApplicationId>,
356    ) -> ApplicationId<Abi>
357    where
358        Abi: ContractAbi,
359        Parameters: Serialize,
360        InstantiationArgument: Serialize,
361    {
362        let parameters = serde_json::to_vec(parameters)
363            .expect("Failed to serialize `Parameters` type for a cross-application call");
364        let argument = serde_json::to_vec(argument).expect(
365            "Failed to serialize `InstantiationArgument` type for a cross-application call",
366        );
367        let converted_application_ids: Vec<_> = required_application_ids
368            .into_iter()
369            .map(From::from)
370            .collect();
371        let application_id = contract_wit::create_application(
372            module_id.into(),
373            &parameters,
374            &argument,
375            &converted_application_ids,
376        );
377        ApplicationId::from(application_id).with_abi::<Abi>()
378    }
379
380    /// Creates a new data blob and returns its hash.
381    pub fn create_data_blob(&mut self, bytes: Vec<u8>) -> DataBlobHash {
382        let hash = contract_wit::create_data_blob(&bytes);
383        hash.into()
384    }
385
386    /// Publishes a module with contract and service bytecode and returns the module ID.
387    pub fn publish_module(
388        &mut self,
389        contract: Bytecode,
390        service: Bytecode,
391        vm_runtime: VmRuntime,
392    ) -> ModuleId {
393        contract_wit::publish_module(&contract.into(), &service.into(), vm_runtime.into()).into()
394    }
395
396    /// Returns the round in which this block was validated.
397    pub fn validation_round(&mut self) -> Option<u32> {
398        contract_wit::validation_round()
399    }
400}
401
402/// A helper type that uses the builder pattern to configure how a message is sent, and then
403/// sends the message once it is dropped.
404#[must_use]
405pub struct MessageBuilder<Message>
406where
407    Message: Serialize,
408{
409    authenticated: bool,
410    is_tracked: bool,
411    grant: Resources,
412    message: Message,
413}
414
415impl<Message> MessageBuilder<Message>
416where
417    Message: Serialize,
418{
419    /// Creates a new [`MessageBuilder`] instance to send the `message` to the `destination`.
420    pub(crate) fn new(message: Message) -> Self {
421        MessageBuilder {
422            authenticated: false,
423            is_tracked: false,
424            grant: Resources::default(),
425            message,
426        }
427    }
428
429    /// Marks the message to be tracked, so that the sender receives the message back if it is
430    /// rejected by the receiver.
431    pub fn with_tracking(mut self) -> Self {
432        self.is_tracked = true;
433        self
434    }
435
436    /// Forwards the authenticated signer with the message.
437    pub fn with_authentication(mut self) -> Self {
438        self.authenticated = true;
439        self
440    }
441
442    /// Forwards a grant of resources so the receiver can use it to pay for receiving the message.
443    pub fn with_grant(mut self, grant: Resources) -> Self {
444        self.grant = grant;
445        self
446    }
447
448    /// Schedules this `Message` to be sent to the `destination`.
449    pub fn send_to(self, destination: ChainId) {
450        let serialized_message =
451            bcs::to_bytes(&self.message).expect("Failed to serialize message to be sent");
452
453        let raw_message = SendMessageRequest {
454            destination,
455            authenticated: self.authenticated,
456            is_tracked: self.is_tracked,
457            grant: self.grant,
458            message: serialized_message,
459        };
460
461        contract_wit::send_message(&raw_message.into())
462    }
463}