use crate::codegen::encoding::create_encoder;
use crate::codegen::{
cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy, ReturnCode},
solana_deploy::solana_deploy,
vartable::Vartable,
Builtin, Expression, Options,
};
use crate::{
sema::ast::{Namespace, StructType, Type},
Target,
};
use num_bigint::{BigInt, Sign};
use num_traits::Zero;
use solang_parser::{pt, pt::Loc};
pub(super) fn function_dispatch(
contract_no: usize,
all_cfg: &[ControlFlowGraph],
ns: &mut Namespace,
opt: &Options,
) -> ControlFlowGraph {
let mut vartab = Vartable::new(ns.next_id);
let mut cfg = ControlFlowGraph::new("solang_dispatch".into(), ASTFunction::None);
let switch_block = cfg.new_basic_block("switch".to_string());
let no_function_matched = cfg.new_basic_block("no_function_matched".to_string());
let argsdata_var = vartab.temp_name("input", &Type::BufferPointer);
let argslen_var = vartab.temp_name("input_len", &Type::Uint(64));
let sol_params =
Expression::FunctionArg(Loc::Codegen, Type::Struct(StructType::SolParameters), 0);
cfg.add(
&mut vartab,
Instr::Set {
res: argsdata_var,
loc: Loc::Codegen,
expr: Expression::Load(
Loc::Codegen,
Type::Ref(Type::BufferPointer.into()),
Expression::StructMember(
Loc::Codegen,
Type::Ref(Type::BufferPointer.into()),
sol_params.clone().into(),
2,
)
.into(),
),
},
);
let argsdata = Expression::Variable(Loc::Codegen, Type::BufferPointer, argsdata_var);
cfg.add(
&mut vartab,
Instr::Set {
res: argslen_var,
loc: Loc::Codegen,
expr: Expression::Load(
Loc::Codegen,
Type::Ref(Type::Uint(64).into()),
Expression::StructMember(
Loc::Codegen,
Type::Ref(Type::Uint(64).into()),
sol_params.into(),
3,
)
.into(),
),
},
);
let argslen = Expression::Variable(Loc::Codegen, Type::Uint(64), argslen_var);
let not_fallback = Expression::MoreEqual(
Loc::Codegen,
argslen.clone().into(),
Expression::NumberLiteral(Loc::Codegen, Type::Uint(64), BigInt::from(8u8)).into(),
);
cfg.add(
&mut vartab,
Instr::BranchCond {
cond: not_fallback,
true_block: switch_block,
false_block: no_function_matched,
},
);
cfg.set_basic_block(switch_block);
let fid = Expression::Builtin(
Loc::Codegen,
vec![Type::Uint(64)],
Builtin::ReadFromBuffer,
vec![
argsdata.clone(),
Expression::NumberLiteral(Loc::Codegen, Type::Uint(64), BigInt::zero()),
],
);
let argsdata = Expression::AdvancePointer {
pointer: Box::new(argsdata),
bytes_offset: Box::new(Expression::NumberLiteral(
Loc::Codegen,
Type::Uint(32),
BigInt::from(8u8),
)),
};
let argslen = Expression::Subtract(
Loc::Codegen,
Type::Uint(64),
false,
Box::new(argslen),
Box::new(Expression::NumberLiteral(
Loc::Codegen,
Type::Uint(64),
BigInt::from(8u8),
)),
);
let magic = vartab.temp_name("magic", &Type::Uint(32));
cfg.add(
&mut vartab,
Instr::LoadStorage {
res: magic,
ty: Type::Uint(32),
storage: Expression::NumberLiteral(Loc::Codegen, Type::Uint(32), 0.into()),
},
);
let mut cases = Vec::new();
for (cfg_no, func_cfg) in all_cfg.iter().enumerate() {
if !func_cfg.public {
continue;
}
let entry = if func_cfg.ty == pt::FunctionTy::Function {
add_function_dispatch_case(
cfg_no,
func_cfg,
magic,
&argsdata,
argslen.clone(),
contract_no,
ns,
&mut vartab,
&mut cfg,
)
} else if func_cfg.ty == pt::FunctionTy::Constructor {
add_constructor_dispatch_case(
contract_no,
cfg_no,
&argsdata,
argslen.clone(),
func_cfg,
ns,
&mut vartab,
&mut cfg,
opt,
)
} else {
continue;
};
cases.push((
Expression::NumberLiteral(
Loc::Codegen,
Type::Uint(64),
BigInt::from_bytes_le(Sign::Plus, &func_cfg.selector),
),
entry,
));
}
cfg.set_basic_block(switch_block);
cfg.add(
&mut vartab,
Instr::Switch {
cond: fid,
cases,
default: no_function_matched,
},
);
cfg.set_basic_block(no_function_matched);
let fallback = all_cfg
.iter()
.enumerate()
.find(|(_, cfg)| cfg.public && cfg.ty == pt::FunctionTy::Fallback);
let receive = all_cfg
.iter()
.enumerate()
.find(|(_, cfg)| cfg.public && cfg.ty == pt::FunctionTy::Receive);
if fallback.is_none() && receive.is_none() {
cfg.add(
&mut vartab,
Instr::ReturnCode {
code: ReturnCode::FunctionSelectorInvalid,
},
);
vartab.finalize(ns, &mut cfg);
return cfg;
}
match fallback {
Some((cfg_no, _)) => {
cfg.add(
&mut vartab,
Instr::Call {
res: vec![],
return_tys: vec![],
args: vec![],
call: InternalCallTy::Static { cfg_no },
},
);
cfg.add(
&mut vartab,
Instr::ReturnCode {
code: ReturnCode::Success,
},
);
}
None => {
cfg.add(
&mut vartab,
Instr::ReturnCode {
code: ReturnCode::InvalidDataError,
},
);
}
}
vartab.finalize(ns, &mut cfg);
cfg
}
fn add_function_dispatch_case(
cfg_no: usize,
func_cfg: &ControlFlowGraph,
magic: usize,
argsdata: &Expression,
argslen: Expression,
contract_no: usize,
ns: &Namespace,
vartab: &mut Vartable,
cfg: &mut ControlFlowGraph,
) -> usize {
let entry = cfg.new_basic_block(format!("function_cfg_{}", cfg_no));
cfg.set_basic_block(entry);
let magic_ok = cfg.new_basic_block("magic_ok".into());
let magic_bad = cfg.new_basic_block("magic_bad".into());
cfg.add(
vartab,
Instr::BranchCond {
cond: Expression::Equal(
Loc::Codegen,
Expression::Variable(Loc::Codegen, Type::Uint(32), magic).into(),
Expression::NumberLiteral(
Loc::Codegen,
Type::Uint(32),
ns.contracts[contract_no].selector().into(),
)
.into(),
),
true_block: magic_ok,
false_block: magic_bad,
},
);
cfg.set_basic_block(magic_bad);
cfg.add(
vartab,
Instr::ReturnCode {
code: ReturnCode::InvalidDataError,
},
);
cfg.set_basic_block(magic_ok);
let truncated_len = Expression::Trunc(Loc::Codegen, Type::Uint(32), Box::new(argslen));
let tys = func_cfg
.params
.iter()
.map(|e| e.ty.clone())
.collect::<Vec<Type>>();
let mut encoder = create_encoder(ns, false);
let decoded = encoder.abi_decode(
&Loc::Codegen,
argsdata,
&tys,
ns,
vartab,
cfg,
Some(truncated_len),
);
let mut returns: Vec<usize> = Vec::with_capacity(func_cfg.returns.len());
let mut return_tys: Vec<Type> = Vec::with_capacity(func_cfg.returns.len());
let mut returns_expr: Vec<Expression> = Vec::with_capacity(func_cfg.returns.len());
for item in func_cfg.returns.iter() {
let new_var = vartab.temp_anonymous(&item.ty);
returns.push(new_var);
return_tys.push(item.ty.clone());
returns_expr.push(Expression::Variable(Loc::Codegen, item.ty.clone(), new_var));
}
cfg.add(
vartab,
Instr::Call {
res: returns,
call: InternalCallTy::Static { cfg_no },
args: decoded,
return_tys,
},
);
if !func_cfg.returns.is_empty() {
let (data, data_len) = encoder.abi_encode(&Loc::Codegen, returns_expr, ns, vartab, cfg);
let zext_len = Expression::ZeroExt(Loc::Codegen, Type::Uint(64), Box::new(data_len));
cfg.add(
vartab,
Instr::ReturnData {
data,
data_len: zext_len,
},
);
} else {
cfg.add(
vartab,
Instr::ReturnData {
data: Expression::AllocDynamicBytes(
Loc::Codegen,
Type::DynamicBytes,
Expression::NumberLiteral(Loc::Codegen, Type::Uint(32), 0.into()).into(),
None,
),
data_len: Expression::NumberLiteral(Loc::Codegen, Type::Uint(64), 0.into()),
},
);
}
entry
}
fn add_constructor_dispatch_case(
contract_no: usize,
cfg_no: usize,
argsdata: &Expression,
argslen: Expression,
func_cfg: &ControlFlowGraph,
ns: &mut Namespace,
vartab: &mut Vartable,
cfg: &mut ControlFlowGraph,
opt: &Options,
) -> usize {
let entry = cfg.new_basic_block(format!("constructor_cfg_{}", cfg_no));
cfg.set_basic_block(entry);
let mut returns: Vec<Expression> = Vec::new();
if !func_cfg.params.is_empty() {
let tys = func_cfg
.params
.iter()
.map(|e| e.ty.clone())
.collect::<Vec<Type>>();
let encoder = create_encoder(ns, false);
let truncated_len = Expression::Trunc(Loc::Codegen, Type::Uint(32), Box::new(argslen));
returns = encoder.abi_decode(
&Loc::Codegen,
argsdata,
&tys,
ns,
vartab,
cfg,
Some(truncated_len),
);
}
if ns.target == Target::Solana {
if let ASTFunction::SolidityFunction(function_no) = func_cfg.function_no {
let func = &ns.functions[function_no];
solana_deploy(func, &returns, contract_no, vartab, cfg, ns, opt);
} else if let Some((func, _)) = &ns.contracts[contract_no].default_constructor {
solana_deploy(func, &returns, contract_no, vartab, cfg, ns, opt);
} else {
unreachable!();
}
}
cfg.add(
vartab,
Instr::Call {
res: vec![],
return_tys: vec![],
call: InternalCallTy::Static {
cfg_no: ns.contracts[contract_no].initializer.unwrap(),
},
args: vec![],
},
);
cfg.add(
vartab,
Instr::Call {
res: vec![],
return_tys: vec![],
call: InternalCallTy::Static { cfg_no },
args: returns,
},
);
cfg.add(
vartab,
Instr::ReturnCode {
code: ReturnCode::Success,
},
);
entry
}