Skip to main content

oasis_runtime_sdk_contracts/
results.rs

1//! Processing of execution results.
2use std::convert::TryInto;
3
4use oasis_contract_sdk_types::{
5    event::Event,
6    message::{Message, NotifyReply, Reply},
7    ExecutionOk,
8};
9use oasis_runtime_sdk::{
10    context::Context,
11    event::etag_for_event,
12    modules::core::API as _,
13    runtime::Runtime,
14    state::CurrentState,
15    subcall::{self, SubcallInfo},
16    types::transaction::CallerAddress,
17};
18
19use crate::{
20    abi::{ExecutionContext, ExecutionResult},
21    types::ContractEvent,
22    wasm, Config, Error, Parameters, MODULE_NAME,
23};
24
25/// Process an execution result by performing gas accounting and returning the inner result.
26pub(crate) fn process_execution_result<C: Context>(
27    _ctx: &C,
28    result: ExecutionResult,
29) -> Result<ExecutionOk, Error> {
30    // The following call should never fail as we accounted for all the gas in advance.
31    <C::Runtime as Runtime>::Core::use_tx_gas(result.gas_used)?;
32
33    result.inner
34}
35
36/// Process a successful execution result.
37pub(crate) fn process_execution_success<Cfg: Config, C: Context>(
38    ctx: &C,
39    params: &Parameters,
40    contract: &wasm::Contract<'_>,
41    result: ExecutionOk,
42) -> Result<Vec<u8>, Error> {
43    // Process events.
44    process_events(contract, result.events)?;
45    // Process subcalls.
46    let result = process_subcalls::<Cfg, C>(ctx, params, contract, result.messages, result.data)?;
47
48    Ok(result)
49}
50
51fn process_events(contract: &wasm::Contract<'_>, events: Vec<Event>) -> Result<(), Error> {
52    // Transform contract events into tags using the SDK scheme.
53    CurrentState::with(|state| {
54        for event in events {
55            state.emit_event_raw(etag_for_event(
56                &if event.module.is_empty() {
57                    format!("{}.{}", MODULE_NAME, contract.code_info.id.as_u64())
58                } else {
59                    format!(
60                        "{}.{}.{}",
61                        MODULE_NAME,
62                        contract.code_info.id.as_u64(),
63                        event.module,
64                    )
65                },
66                event.code,
67                cbor::to_value(ContractEvent {
68                    id: contract.instance_info.id,
69                    data: event.data,
70                }),
71            ));
72        }
73    });
74
75    Ok(())
76}
77
78fn process_subcalls<Cfg: Config, C: Context>(
79    ctx: &C,
80    params: &Parameters,
81    contract: &wasm::Contract<'_>,
82    messages: Vec<Message>,
83    data: Vec<u8>,
84) -> Result<Vec<u8>, Error> {
85    // By default the resulting data is what the call returned. Message reply processing may
86    // overwrite this data when it is non-empty.
87    let mut result_data = data;
88
89    // Charge gas for each emitted message.
90    <C::Runtime as Runtime>::Core::use_tx_gas(
91        params
92            .gas_costs
93            .subcall_dispatch
94            .saturating_mul(messages.len() as u64),
95    )?;
96
97    // Make sure the number of subcalls is within limits.
98    let message_count = messages
99        .len()
100        .try_into()
101        .map_err(|_| Error::TooManySubcalls(u16::MAX, params.max_subcall_count))?;
102    if message_count > params.max_subcall_count {
103        return Err(Error::TooManySubcalls(
104            message_count,
105            params.max_subcall_count,
106        ));
107    }
108
109    // Properly propagate original call format and read-only flag.
110    let (orig_call_format, orig_read_only) =
111        CurrentState::with_env(|env| (env.tx_call_format(), env.is_read_only()));
112
113    // Process emitted messages recursively.
114    for msg in messages {
115        match msg {
116            Message::Call {
117                id,
118                data,
119                reply,
120                method,
121                body,
122                max_gas,
123            } => {
124                // Compute the amount of gas that can be used.
125                let remaining_gas = <C::Runtime as Runtime>::Core::remaining_tx_gas();
126                let max_gas = max_gas.unwrap_or(remaining_gas);
127                let max_gas = if max_gas > remaining_gas {
128                    remaining_gas
129                } else {
130                    max_gas
131                };
132
133                let result = subcall::call(
134                    ctx,
135                    SubcallInfo {
136                        caller: CallerAddress::Address(contract.instance_info.address()),
137                        method,
138                        body,
139                        max_depth: params.max_subcall_depth,
140                        max_gas,
141                        read_only: orig_read_only,
142                    },
143                    subcall::AllowAllValidator,
144                )?;
145
146                // Use any gas that was used inside the child context. This should never fail as we
147                // preconfigured the amount of available gas.
148                <C::Runtime as Runtime>::Core::use_tx_gas(result.gas_used)?;
149
150                // Process replies based on filtering criteria.
151                let result = result.call_result;
152                match (reply, result.is_success()) {
153                    (NotifyReply::OnError, false)
154                    | (NotifyReply::OnSuccess, true)
155                    | (NotifyReply::Always, _) => {
156                        // Construct and process reply.
157                        let reply = Reply::Call {
158                            id,
159                            result: result.into(),
160                            data,
161                        };
162                        let mut exec_ctx = ExecutionContext::new(
163                            params,
164                            contract.code_info,
165                            contract.instance_info,
166                            <C::Runtime as Runtime>::Core::remaining_tx_gas(),
167                            CurrentState::with_env(|env| env.tx_caller_address()),
168                            orig_read_only,
169                            orig_call_format,
170                            ctx,
171                        );
172                        let reply_result =
173                            wasm::handle_reply::<Cfg, C>(&mut exec_ctx, contract, reply);
174                        let reply_result = process_execution_result(ctx, reply_result)?;
175                        let reply_result = process_execution_success::<Cfg, C>(
176                            ctx,
177                            params,
178                            contract,
179                            reply_result,
180                        )?;
181
182                        // If there is a non-empty reply, it overwrites the returned data.
183                        if !reply_result.is_empty() {
184                            result_data = reply_result;
185                        }
186                    }
187                    _ => {}
188                }
189            }
190
191            // Message not supported.
192            _ => return Err(Error::Unsupported),
193        }
194    }
195
196    Ok(result_data)
197}