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                    },
142                    subcall::AllowAllValidator,
143                )?;
144
145                // Use any gas that was used inside the child context. This should never fail as we
146                // preconfigured the amount of available gas.
147                <C::Runtime as Runtime>::Core::use_tx_gas(result.gas_used)?;
148
149                // Process replies based on filtering criteria.
150                let result = result.call_result;
151                match (reply, result.is_success()) {
152                    (NotifyReply::OnError, false)
153                    | (NotifyReply::OnSuccess, true)
154                    | (NotifyReply::Always, _) => {
155                        // Construct and process reply.
156                        let reply = Reply::Call {
157                            id,
158                            result: result.into(),
159                            data,
160                        };
161                        let mut exec_ctx = ExecutionContext::new(
162                            params,
163                            contract.code_info,
164                            contract.instance_info,
165                            <C::Runtime as Runtime>::Core::remaining_tx_gas(),
166                            CurrentState::with_env(|env| env.tx_caller_address()),
167                            orig_read_only,
168                            orig_call_format,
169                            ctx,
170                        );
171                        let reply_result =
172                            wasm::handle_reply::<Cfg, C>(&mut exec_ctx, contract, reply);
173                        let reply_result = process_execution_result(ctx, reply_result)?;
174                        let reply_result = process_execution_success::<Cfg, C>(
175                            ctx,
176                            params,
177                            contract,
178                            reply_result,
179                        )?;
180
181                        // If there is a non-empty reply, it overwrites the returned data.
182                        if !reply_result.is_empty() {
183                            result_data = reply_result;
184                        }
185                    }
186                    _ => {}
187                }
188            }
189
190            // Message not supported.
191            _ => return Err(Error::Unsupported),
192        }
193    }
194
195    Ok(result_data)
196}