oasis_runtime_sdk/
subcall.rs1use 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 static SUBCALL_STACK: RefCell<SubcallStack> = RefCell::new(SubcallStack::new());
17}
18
19pub trait Validator {
21 fn validate(&self, info: &SubcallInfo) -> Result<(), Error>;
23}
24
25pub struct AllowAllValidator;
27
28impl Validator for AllowAllValidator {
29 fn validate(&self, _info: &SubcallInfo) -> Result<(), Error> {
30 Ok(())
31 }
32}
33
34#[derive(Clone, Debug)]
36pub struct SubcallInfo {
37 pub caller: CallerAddress,
39 pub method: String,
41 pub body: cbor::Value,
43 pub max_depth: u16,
45 pub max_gas: u64,
47}
48
49#[derive(Debug)]
51pub struct SubcallResult {
52 pub call_result: CallResult,
54 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
101pub fn get_current_subcall_depth<C: Context>(_ctx: &C) -> u16 {
103 SUBCALL_STACK.with(|ss| ss.borrow().depth())
104}
105
106pub fn call<C: Context, V: Validator + 'static>(
108 ctx: &C,
109 info: SubcallInfo,
110 validator: V,
111) -> Result<SubcallResult, Error> {
112 validator.validate(&info)?;
114
115 SUBCALL_STACK.with(|ss| {
117 let mut stack = ss.borrow_mut();
118
119 if stack.depth() >= info.max_depth {
121 return Err(Error::CallDepthExceeded(stack.depth() + 1, info.max_depth));
122 }
123
124 stack.run_validators(&info)?;
126
127 stack.push(SubcallStackEntry {
129 validator: Box::new(validator) as Box<dyn Validator>,
130 });
131
132 Ok(())
133 })?;
134 let _guard = SubcallStackGuard; 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 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 gas: info.max_gas,
155 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(); let (call_result, gas) = CurrentState::with_transaction_opts(
166 Options::new()
167 .with_internal(true)
168 .with_tx(TransactionWithMeta::internal(tx)),
169 || {
170 let (result, _) = dispatcher::Dispatcher::<C::Runtime>::dispatch_tx_call(
172 &ctx.clone(), call,
174 &Default::default(),
175 );
176 let gas = <C::Runtime as Runtime>::Core::remaining_tx_gas();
178
179 if result.is_success() {
182 TransactionResult::Commit((result, gas))
183 } else {
184 TransactionResult::Rollback((result, gas))
186 }
187 },
188 );
189
190 let gas_used = info.max_gas.saturating_sub(gas);
192
193 Ok(SubcallResult {
194 call_result,
195 gas_used,
196 })
197}