use crate::{
codegen::{
cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy, ReturnCode},
encoding::{abi_decode, abi_encode},
revert::log_runtime_error,
vartable::Vartable,
Builtin, Expression, Options,
},
sema::ast::{Namespace, Parameter, Type, Type::Uint},
};
use num_bigint::{BigInt, Sign};
use solang_parser::pt::{FunctionTy, Loc::Codegen};
use std::fmt::{Display, Formatter, Result};
pub enum DispatchType {
Deploy,
Call,
}
impl Display for DispatchType {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::Deploy => f.write_str("polkadot_deploy_dispatch"),
Self::Call => f.write_str("polkadot_call_dispatch"),
}
}
}
impl From<FunctionTy> for DispatchType {
fn from(value: FunctionTy) -> Self {
match value {
FunctionTy::Constructor => Self::Deploy,
FunctionTy::Function => Self::Call,
_ => unreachable!("only constructors and functions have corresponding dispatch types"),
}
}
}
pub(crate) fn function_dispatch(
_contract_no: usize,
all_cfg: &[ControlFlowGraph],
ns: &mut Namespace,
opt: &Options,
) -> Vec<ControlFlowGraph> {
vec![
Dispatch::new(all_cfg, ns, opt, FunctionTy::Constructor).build(),
Dispatch::new(all_cfg, ns, opt, FunctionTy::Function).build(),
]
}
struct Dispatch<'a> {
start: usize,
input_len: usize,
input_ptr: Expression,
value: usize,
vartab: Vartable,
cfg: ControlFlowGraph,
all_cfg: &'a [ControlFlowGraph],
ns: &'a mut Namespace,
selector_len: Box<Expression>,
opt: &'a Options,
ty: FunctionTy,
}
fn new_cfg(ns: &Namespace, ty: FunctionTy) -> ControlFlowGraph {
let mut cfg = ControlFlowGraph::new(DispatchType::from(ty).to_string(), ASTFunction::None);
let input_ptr = Parameter {
loc: Codegen,
id: None,
ty: Type::BufferPointer,
ty_loc: None,
indexed: false,
readonly: true,
infinite_size: false,
recursive: false,
annotation: None,
};
let mut input_len = input_ptr.clone();
input_len.ty = Uint(32);
let mut value = input_ptr.clone();
value.ty = ns.value_type();
let mut selector_ptr = input_ptr.clone();
selector_ptr.ty = Type::Ref(Uint(8 * ns.target.selector_length() as u16).into());
cfg.params = vec![input_ptr, input_len, value, selector_ptr].into();
cfg
}
impl<'a> Dispatch<'a> {
fn new(
all_cfg: &'a [ControlFlowGraph],
ns: &'a mut Namespace,
opt: &'a Options,
ty: FunctionTy,
) -> Self {
let mut vartab = Vartable::new(ns.next_id);
let mut cfg = new_cfg(ns, ty);
let input_len = vartab.temp_name("input_len", &Uint(32));
cfg.add(
&mut vartab,
Instr::Set {
loc: Codegen,
res: input_len,
expr: Expression::FunctionArg {
loc: Codegen,
ty: Uint(32),
arg_no: 1,
},
},
);
let value = vartab.temp_name("value", &ns.value_type());
cfg.add(
&mut vartab,
Instr::Set {
loc: Codegen,
res: value,
expr: Expression::FunctionArg {
loc: Codegen,
ty: ns.value_type(),
arg_no: 2,
},
},
);
let input_ptr_var = vartab.temp_name("input_ptr", &Type::BufferPointer);
cfg.add(
&mut vartab,
Instr::Set {
loc: Codegen,
res: input_ptr_var,
expr: Expression::FunctionArg {
loc: Codegen,
ty: Type::BufferPointer,
arg_no: 0,
},
},
);
let input_ptr = Expression::Variable {
loc: Codegen,
ty: Type::BufferPointer,
var_no: input_ptr_var,
};
let selector_len: Box<Expression> = Expression::NumberLiteral {
loc: Codegen,
ty: Uint(32),
value: ns.target.selector_length().into(),
}
.into();
let input_ptr = Expression::AdvancePointer {
pointer: input_ptr.into(),
bytes_offset: selector_len.clone(),
};
Self {
start: cfg.new_basic_block("start_dispatch".into()),
input_len,
input_ptr,
vartab,
value,
cfg,
all_cfg,
ns,
selector_len,
opt,
ty,
}
}
fn build(mut self) -> ControlFlowGraph {
let cond = Expression::Less {
loc: Codegen,
signed: false,
left: Expression::Variable {
loc: Codegen,
ty: Uint(32),
var_no: self.input_len,
}
.into(),
right: self.selector_len.clone(),
};
let default = self.cfg.new_basic_block("fb_or_recv".into());
self.add(Instr::BranchCond {
cond,
true_block: default,
false_block: self.start,
});
let selector_ty = Uint(8 * self.ns.target.selector_length() as u16);
let cases = self
.all_cfg
.iter()
.enumerate()
.filter_map(|(func_no, func_cfg)| {
if func_cfg.ty == self.ty && func_cfg.public {
let selector = BigInt::from_bytes_le(Sign::Plus, &func_cfg.selector);
let case = Expression::NumberLiteral {
loc: Codegen,
ty: selector_ty.clone(),
value: selector,
};
Some((case, self.dispatch_case(func_no)))
} else {
None
}
})
.collect();
self.cfg.set_basic_block(self.start);
let selector_var = self.vartab.temp_name("selector", &selector_ty);
self.add(Instr::Set {
loc: Codegen,
res: selector_var,
expr: Expression::Builtin {
loc: Codegen,
tys: vec![selector_ty.clone()],
kind: Builtin::ReadFromBuffer,
args: vec![
Expression::FunctionArg {
loc: Codegen,
ty: Type::BufferPointer,
arg_no: 0,
},
Expression::NumberLiteral {
loc: Codegen,
ty: selector_ty.clone(),
value: 0.into(),
},
],
},
});
let selector = Expression::Variable {
loc: Codegen,
ty: selector_ty.clone(),
var_no: selector_var,
};
self.add(Instr::Store {
dest: Expression::FunctionArg {
loc: Codegen,
ty: selector_ty.clone(),
arg_no: 3,
},
data: selector.clone(),
});
self.add(Instr::Switch {
cond: selector,
cases,
default,
});
self.cfg.set_basic_block(default);
self.fallback_or_receive();
self.vartab.finalize(self.ns, &mut self.cfg);
self.cfg
}
fn dispatch_case(&mut self, func_no: usize) -> usize {
let case_bb = self.cfg.new_basic_block(format!("func_{func_no}_dispatch"));
self.cfg.set_basic_block(case_bb);
self.abort_if_value_transfer(func_no);
let cfg = &self.all_cfg[func_no];
let mut args = vec![];
if !cfg.params.is_empty() {
let buf_len = Expression::Variable {
loc: Codegen,
ty: Uint(32),
var_no: self.input_len,
};
let arg_len = Expression::Subtract {
loc: Codegen,
ty: Uint(32),
overflowing: false,
left: buf_len.into(),
right: self.selector_len.clone(),
};
args = abi_decode(
&Codegen,
&self.input_ptr,
&cfg.params.iter().map(|p| p.ty.clone()).collect::<Vec<_>>(),
self.ns,
&mut self.vartab,
&mut self.cfg,
Some(Expression::Trunc {
loc: Codegen,
ty: Uint(32),
expr: arg_len.into(),
}),
);
}
let mut returns: Vec<usize> = Vec::with_capacity(cfg.returns.len());
let mut return_tys: Vec<Type> = Vec::with_capacity(cfg.returns.len());
let mut returns_expr: Vec<Expression> = Vec::with_capacity(cfg.returns.len());
for item in cfg.returns.iter() {
let new_var = self.vartab.temp_anonymous(&item.ty);
returns.push(new_var);
return_tys.push(item.ty.clone());
returns_expr.push(Expression::Variable {
loc: Codegen,
ty: item.ty.clone(),
var_no: new_var,
});
}
self.add(Instr::Call {
res: returns,
call: InternalCallTy::Static { cfg_no: func_no },
args,
return_tys,
});
if cfg.returns.is_empty() {
let data_len = Expression::NumberLiteral {
loc: Codegen,
ty: Uint(32),
value: 0.into(),
};
let data = Expression::AllocDynamicBytes {
loc: Codegen,
ty: Type::DynamicBytes,
size: data_len.clone().into(),
initializer: None,
};
self.add(Instr::ReturnData { data, data_len })
} else {
let (data, data_len) = abi_encode(
&Codegen,
returns_expr,
self.ns,
&mut self.vartab,
&mut self.cfg,
false,
);
self.add(Instr::ReturnData { data, data_len });
}
case_bb
}
fn abort_if_value_transfer(&mut self, func_no: usize) {
if !self.all_cfg[func_no].nonpayable {
return;
}
let true_block = self
.cfg
.new_basic_block(format!("func_{func_no}_got_value"));
let false_block = self.cfg.new_basic_block(format!("func_{func_no}_no_value"));
self.add(Instr::BranchCond {
cond: Expression::More {
loc: Codegen,
signed: false,
left: Expression::Variable {
loc: Codegen,
ty: self.ns.value_type(),
var_no: self.value,
}
.into(),
right: Expression::NumberLiteral {
loc: Codegen,
ty: self.ns.value_type(),
value: 0.into(),
}
.into(),
},
true_block,
false_block,
});
self.cfg.set_basic_block(true_block);
let function_name = self.all_cfg[func_no].name.split("::").last().unwrap();
let function_type = self.all_cfg[func_no].ty;
log_runtime_error(
self.opt.log_runtime_errors,
&format!("runtime_error: non payable {function_type} {function_name} received value"),
Codegen,
&mut self.cfg,
&mut self.vartab,
self.ns,
);
self.add(Instr::AssertFailure { encoded_args: None });
self.cfg.set_basic_block(false_block);
}
fn fallback_or_receive(&mut self) {
let (fallback_cfg, receive_cfg) = self.all_cfg.iter().enumerate().fold(
(None, None),
|(mut fallback_cfg, mut receive_cfg), (no, cfg)| {
match cfg.ty {
FunctionTy::Fallback if cfg.public => fallback_cfg = Some(no),
FunctionTy::Receive if cfg.public => receive_cfg = Some(no),
_ => {}
}
(fallback_cfg, receive_cfg)
},
);
if (fallback_cfg.is_none() && receive_cfg.is_none()) || self.ty == FunctionTy::Constructor {
return self.selector_invalid();
}
let fallback_block = self.cfg.new_basic_block("fallback".into());
let receive_block = self.cfg.new_basic_block("receive".into());
self.add(Instr::BranchCond {
cond: Expression::More {
loc: Codegen,
signed: false,
left: Expression::Variable {
loc: Codegen,
ty: self.ns.value_type(),
var_no: self.value,
}
.into(),
right: Expression::NumberLiteral {
loc: Codegen,
ty: self.ns.value_type(),
value: 0.into(),
}
.into(),
},
true_block: receive_block,
false_block: fallback_block,
});
self.cfg.set_basic_block(fallback_block);
if let Some(cfg_no) = fallback_cfg {
self.add(Instr::Call {
res: vec![],
return_tys: vec![],
call: InternalCallTy::Static { cfg_no },
args: vec![],
});
let data_len = Expression::NumberLiteral {
loc: Codegen,
ty: Uint(32),
value: 0.into(),
};
let data = Expression::AllocDynamicBytes {
loc: Codegen,
ty: Type::DynamicBytes,
size: data_len.clone().into(),
initializer: None,
};
self.add(Instr::ReturnData { data, data_len })
} else {
self.selector_invalid();
}
self.cfg.set_basic_block(receive_block);
if let Some(cfg_no) = receive_cfg {
self.add(Instr::Call {
res: vec![],
return_tys: vec![],
call: InternalCallTy::Static { cfg_no },
args: vec![],
});
let data_len = Expression::NumberLiteral {
loc: Codegen,
ty: Uint(32),
value: 0.into(),
};
let data = Expression::AllocDynamicBytes {
loc: Codegen,
ty: Type::DynamicBytes,
size: data_len.clone().into(),
initializer: None,
};
self.add(Instr::ReturnData { data, data_len })
} else {
self.selector_invalid()
}
}
fn selector_invalid(&mut self) {
let code = ReturnCode::FunctionSelectorInvalid;
self.add(Instr::ReturnCode { code });
}
fn add(&mut self, ins: Instr) {
self.cfg.add(&mut self.vartab, ins);
}
}