oasis_runtime_sdk/
subcall.rs

1//! Subcall dispatch.
2use std::cell::RefCell;
3
4use crate::{
5    context::Context,
6    dispatcher,
7    module::CallResult,
8    modules::core::{Error, API as _},
9    runtime::Runtime,
10    state::{CurrentState, Options, TransactionResult, TransactionWithMeta},
11    types::{token, transaction, transaction::CallerAddress},
12};
13
14thread_local! {
15    /// The subcall stack for tracking depth and other metadata.
16    static SUBCALL_STACK: RefCell<SubcallStack> = RefCell::new(SubcallStack::new());
17}
18
19/// Subcall validator.
20pub trait Validator {
21    /// Validate a subcall before it is performed.
22    fn validate(&self, info: &SubcallInfo) -> Result<(), Error>;
23}
24
25/// A validator which allows everything.
26pub struct AllowAllValidator;
27
28impl Validator for AllowAllValidator {
29    fn validate(&self, _info: &SubcallInfo) -> Result<(), Error> {
30        Ok(())
31    }
32}
33
34/// Information about a subcall to be dispatched.
35#[derive(Clone, Debug)]
36pub struct SubcallInfo {
37    /// Address of the caller.
38    pub caller: CallerAddress,
39    /// Method to call.
40    pub method: String,
41    /// Subcall body.
42    pub body: cbor::Value,
43    /// Maximum subcall depth.
44    pub max_depth: u16,
45    /// Maximum gas amount that can be consumed.
46    pub max_gas: u64,
47}
48
49/// Result of dispatching a subcall.
50#[derive(Debug)]
51pub struct SubcallResult {
52    /// Result of the subcall.
53    pub call_result: CallResult,
54    /// Gas used by the subcall.
55    pub gas_used: u64,
56}
57
58struct SubcallStackEntry {
59    validator: Box<dyn Validator>,
60}
61
62struct SubcallStack {
63    stack: Vec<SubcallStackEntry>,
64}
65
66impl SubcallStack {
67    fn new() -> Self {
68        Self { stack: Vec::new() }
69    }
70
71    fn depth(&self) -> u16 {
72        self.stack.len() as u16
73    }
74
75    fn push(&mut self, entry: SubcallStackEntry) {
76        self.stack.push(entry);
77    }
78
79    fn pop(&mut self) {
80        self.stack.pop();
81    }
82
83    fn run_validators(&self, info: &SubcallInfo) -> Result<(), Error> {
84        for entry in &self.stack {
85            entry.validator.validate(info)?;
86        }
87        Ok(())
88    }
89}
90
91struct SubcallStackGuard;
92
93impl Drop for SubcallStackGuard {
94    fn drop(&mut self) {
95        SUBCALL_STACK.with(|ss| {
96            ss.borrow_mut().pop();
97        });
98    }
99}
100
101/// The current subcall depth.
102pub fn get_current_subcall_depth<C: Context>(_ctx: &C) -> u16 {
103    SUBCALL_STACK.with(|ss| ss.borrow().depth())
104}
105
106/// Perform a subcall.
107pub fn call<C: Context, V: Validator + 'static>(
108    ctx: &C,
109    info: SubcallInfo,
110    validator: V,
111) -> Result<SubcallResult, Error> {
112    // Run validator first.
113    validator.validate(&info)?;
114
115    // Update the subcall stack after doing validation.
116    SUBCALL_STACK.with(|ss| {
117        let mut stack = ss.borrow_mut();
118
119        // Ensure the call depth is not too large.
120        if stack.depth() >= info.max_depth {
121            return Err(Error::CallDepthExceeded(stack.depth() + 1, info.max_depth));
122        }
123
124        // Run existing validators.
125        stack.run_validators(&info)?;
126
127        // Push subcall to stack.
128        stack.push(SubcallStackEntry {
129            validator: Box::new(validator) as Box<dyn Validator>,
130        });
131
132        Ok(())
133    })?;
134    let _guard = SubcallStackGuard; // Ensure subcall is popped from stack.
135
136    // Generate an internal transaction.
137    let tx = transaction::Transaction {
138        version: transaction::LATEST_TRANSACTION_VERSION,
139        call: transaction::Call {
140            format: transaction::CallFormat::Plain,
141            method: info.method,
142            body: info.body,
143            ..Default::default()
144        },
145        auth_info: transaction::AuthInfo {
146            signer_info: vec![transaction::SignerInfo {
147                // The call is being performed on the caller's behalf.
148                address_spec: transaction::AddressSpec::Internal(info.caller),
149                nonce: 0,
150            }],
151            fee: transaction::Fee {
152                amount: token::BaseUnits::new(0, token::Denomination::NATIVE),
153                // Limit gas usage inside the child context to the allocated maximum.
154                gas: info.max_gas,
155                // Propagate consensus message limit.
156                consensus_messages: CurrentState::with(|state| state.emitted_messages_max(ctx)),
157                proxy: None,
158            },
159            ..Default::default()
160        },
161    };
162    let call = tx.call.clone(); // TODO: Avoid clone.
163
164    // Execute a transaction in a child context.
165    let (call_result, gas) = CurrentState::with_transaction_opts(
166        Options::new()
167            .with_internal(true)
168            .with_tx(TransactionWithMeta::internal(tx)),
169        || {
170            // Dispatch the call.
171            let (result, _) = dispatcher::Dispatcher::<C::Runtime>::dispatch_tx_call(
172                &ctx.clone(), // Must clone to avoid infinite type.
173                call,
174                &Default::default(),
175            );
176            // Retrieve remaining gas.
177            let gas = <C::Runtime as Runtime>::Core::remaining_tx_gas();
178
179            // Commit store and return emitted tags and messages on successful dispatch,
180            // otherwise revert state and ignore any emitted events/messages.
181            if result.is_success() {
182                TransactionResult::Commit((result, gas))
183            } else {
184                // Ignore tags/messages on failure.
185                TransactionResult::Rollback((result, gas))
186            }
187        },
188    );
189
190    // Compute the amount of gas used.
191    let gas_used = info.max_gas.saturating_sub(gas);
192
193    Ok(SubcallResult {
194        call_result,
195        gas_used,
196    })
197}