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::BaseUnits, 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 pub read_only: bool,
49}
50
51#[derive(Debug)]
53pub struct SubcallResult {
54 pub call_result: CallResult,
56 pub gas_used: u64,
58}
59
60struct SubcallStackEntry {
61 validator: Box<dyn Validator>,
62}
63
64struct SubcallStack {
65 stack: Vec<SubcallStackEntry>,
66}
67
68impl SubcallStack {
69 fn new() -> Self {
70 Self { stack: Vec::new() }
71 }
72
73 fn depth(&self) -> u16 {
74 self.stack.len() as u16
75 }
76
77 fn push(&mut self, entry: SubcallStackEntry) {
78 self.stack.push(entry);
79 }
80
81 fn pop(&mut self) {
82 self.stack.pop();
83 }
84
85 fn run_validators(&self, info: &SubcallInfo) -> Result<(), Error> {
86 for entry in &self.stack {
87 entry.validator.validate(info)?;
88 }
89 Ok(())
90 }
91}
92
93struct SubcallStackGuard;
94
95impl Drop for SubcallStackGuard {
96 fn drop(&mut self) {
97 SUBCALL_STACK.with(|ss| {
98 ss.borrow_mut().pop();
99 });
100 }
101}
102
103pub fn get_current_subcall_depth<C: Context>(_ctx: &C) -> u16 {
105 SUBCALL_STACK.with(|ss| ss.borrow().depth())
106}
107
108pub fn call<C: Context, V: Validator + 'static>(
110 ctx: &C,
111 info: SubcallInfo,
112 validator: V,
113) -> Result<SubcallResult, Error> {
114 validator.validate(&info)?;
116
117 SUBCALL_STACK.with(|ss| {
119 let mut stack = ss.borrow_mut();
120
121 if stack.depth() >= info.max_depth {
123 return Err(Error::CallDepthExceeded(stack.depth() + 1, info.max_depth));
124 }
125
126 stack.run_validators(&info)?;
128
129 stack.push(SubcallStackEntry {
131 validator: Box::new(validator) as Box<dyn Validator>,
132 });
133
134 Ok(())
135 })?;
136 let _guard = SubcallStackGuard; let tx = transaction::Transaction {
140 version: transaction::LATEST_TRANSACTION_VERSION,
141 call: transaction::Call {
142 format: transaction::CallFormat::Plain,
143 method: info.method,
144 body: info.body,
145 read_only: info.read_only,
146 },
147 auth_info: transaction::AuthInfo {
148 signer_info: vec![transaction::SignerInfo {
149 address_spec: transaction::AddressSpec::Internal(info.caller),
151 nonce: 0,
152 }],
153 fee: transaction::Fee {
154 amount: BaseUnits::native(0),
155 gas: info.max_gas,
157 consensus_messages: CurrentState::with(|state| state.emitted_messages_max(ctx)),
159 proxy: None,
160 },
161 ..Default::default()
162 },
163 };
164 let call = tx.call.clone(); let (call_result, gas) = CurrentState::with_transaction_opts(
168 Options::new()
169 .with_internal(true)
170 .with_tx(TransactionWithMeta::internal(tx)),
171 || {
172 let (result, _) = dispatcher::Dispatcher::<C::Runtime>::dispatch_tx_call(
174 &ctx.clone(), call,
176 &Default::default(),
177 );
178 let gas = <C::Runtime as Runtime>::Core::remaining_tx_gas();
180
181 if result.is_success() {
184 TransactionResult::Commit((result, gas))
185 } else {
186 TransactionResult::Rollback((result, gas))
188 }
189 },
190 );
191
192 let gas_used = info.max_gas.saturating_sub(gas);
194
195 Ok(SubcallResult {
196 call_result,
197 gas_used,
198 })
199}