use std::cell::RefCell;
use crate::{
context::Context,
dispatcher,
module::CallResult,
modules::core::{Error, API as _},
runtime::Runtime,
state::{CurrentState, Options, TransactionResult, TransactionWithMeta},
types::{token, transaction, transaction::CallerAddress},
};
thread_local! {
static SUBCALL_STACK: RefCell<SubcallStack> = RefCell::new(SubcallStack::new());
}
pub trait Validator {
fn validate(&self, info: &SubcallInfo) -> Result<(), Error>;
}
pub struct AllowAllValidator;
impl Validator for AllowAllValidator {
fn validate(&self, _info: &SubcallInfo) -> Result<(), Error> {
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct SubcallInfo {
pub caller: CallerAddress,
pub method: String,
pub body: cbor::Value,
pub max_depth: u16,
pub max_gas: u64,
}
#[derive(Debug)]
pub struct SubcallResult {
pub call_result: CallResult,
pub gas_used: u64,
}
struct SubcallStackEntry {
validator: Box<dyn Validator>,
}
struct SubcallStack {
stack: Vec<SubcallStackEntry>,
}
impl SubcallStack {
fn new() -> Self {
Self { stack: Vec::new() }
}
fn depth(&self) -> u16 {
self.stack.len() as u16
}
fn push(&mut self, entry: SubcallStackEntry) {
self.stack.push(entry);
}
fn pop(&mut self) {
self.stack.pop();
}
fn run_validators(&self, info: &SubcallInfo) -> Result<(), Error> {
for entry in &self.stack {
entry.validator.validate(info)?;
}
Ok(())
}
}
struct SubcallStackGuard;
impl Drop for SubcallStackGuard {
fn drop(&mut self) {
SUBCALL_STACK.with(|ss| {
ss.borrow_mut().pop();
});
}
}
pub fn get_current_subcall_depth<C: Context>(_ctx: &C) -> u16 {
SUBCALL_STACK.with(|ss| ss.borrow().depth())
}
pub fn call<C: Context, V: Validator + 'static>(
ctx: &C,
info: SubcallInfo,
validator: V,
) -> Result<SubcallResult, Error> {
validator.validate(&info)?;
SUBCALL_STACK.with(|ss| {
let mut stack = ss.borrow_mut();
if stack.depth() >= info.max_depth {
return Err(Error::CallDepthExceeded(stack.depth() + 1, info.max_depth));
}
stack.run_validators(&info)?;
stack.push(SubcallStackEntry {
validator: Box::new(validator) as Box<dyn Validator>,
});
Ok(())
})?;
let _guard = SubcallStackGuard; let tx = transaction::Transaction {
version: transaction::LATEST_TRANSACTION_VERSION,
call: transaction::Call {
format: transaction::CallFormat::Plain,
method: info.method,
body: info.body,
..Default::default()
},
auth_info: transaction::AuthInfo {
signer_info: vec![transaction::SignerInfo {
address_spec: transaction::AddressSpec::Internal(info.caller),
nonce: 0,
}],
fee: transaction::Fee {
amount: token::BaseUnits::new(0, token::Denomination::NATIVE),
gas: info.max_gas,
consensus_messages: CurrentState::with(|state| state.emitted_messages_max(ctx)),
proxy: None,
},
..Default::default()
},
};
let call = tx.call.clone(); let (call_result, gas) = CurrentState::with_transaction_opts(
Options::new()
.with_internal(true)
.with_tx(TransactionWithMeta::internal(tx)),
|| {
let (result, _) = dispatcher::Dispatcher::<C::Runtime>::dispatch_tx_call(
&ctx.clone(), call,
&Default::default(),
);
let gas = <C::Runtime as Runtime>::Core::remaining_tx_gas();
if result.is_success() {
TransactionResult::Commit((result, gas))
} else {
TransactionResult::Rollback((result, gas))
}
},
);
let gas_used = info.max_gas.saturating_sub(gas);
Ok(SubcallResult {
call_result,
gas_used,
})
}