pub(crate) mod dispatch;
pub(crate) mod encoding;
pub(crate) mod events;
use self::encoding::{soroban_decode, soroban_decode_arg, soroban_encode, soroban_encode_arg};
use self::events::SorobanEventEmitter;
use crate::codegen::cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy};
use crate::codegen::error::CodegenError;
use crate::codegen::expression::{expression, load_storage};
use crate::codegen::interface::{EventEmitter, TargetCodegen};
use crate::codegen::storage::storage_slots_array_push;
use crate::codegen::vartable::Vartable;
use crate::codegen::Options;
use crate::codegen::{Expression, HostFunctions};
use crate::sema::ast;
use crate::sema::ast::{Function, Namespace, RetrieveType, StructType, Type};
use num_bigint::{BigInt, Sign};
use num_traits::{ToPrimitive, Zero};
use solang_parser::helpers::CodeLocation;
use solang_parser::{diagnostics::Diagnostic, pt};
pub(crate) struct SorobanTarget;
impl TargetCodegen for SorobanTarget {
fn validate_contract(&self, contract_no: usize, ns: &mut Namespace) {
validate_accessor_abi_types(contract_no, ns);
if ns.diagnostics.any_errors() {
return;
}
validate_event_abi_types(contract_no, ns);
}
fn validate_cfgs(&self, all_cfg: &[ControlFlowGraph], ns: &mut Namespace) {
validate_abi_types(all_cfg, ns);
}
fn function_dispatch(
&self,
contract_no: usize,
all_cfg: &mut [ControlFlowGraph],
ns: &mut Namespace,
opt: &Options,
) -> Vec<ControlFlowGraph> {
dispatch::function_dispatch(contract_no, all_cfg, ns, opt)
}
fn lower_storage_array_length(
&self,
loc: &pt::Loc,
ty: &Type,
array: Expression,
elem_ty: &Type,
_cfg: &mut ControlFlowGraph,
_vartab: &mut Vartable,
_ns: &Namespace,
) -> Expression {
Expression::StorageArrayLength {
loc: *loc,
ty: ty.clone(),
array: Box::new(array),
elem_ty: elem_ty.clone(),
}
}
fn lower_load(
&self,
load: Expression,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
ns: &Namespace,
) -> Expression {
if let Expression::Load { ref expr, .. } = load {
if let Type::Ref(inner) = expr.ty() {
if matches!(inner.as_ref(), Type::SorobanHandle(_)) {
return soroban_decode_arg(load, cfg, vartab, ns, None);
}
}
}
load
}
fn prepare_storage_value(
&self,
value: Expression,
dest: &Expression,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
ns: &Namespace,
) -> Expression {
if let Type::Ref(inner) = dest.ty() {
if !matches!(inner.as_ref(), Type::SorobanHandle(_)) {
return value;
}
}
soroban_encode_arg(value, cfg, vartab, ns)
}
fn default_storage_value(
&self,
loc: &pt::Loc,
ty: &Type,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
ns: &Namespace,
) -> Option<Expression> {
match ty {
Type::DynamicBytes => Some(soroban_bytes_new(loc, cfg, vartab)),
Type::String => {
let buf = vartab.temp_name("empty_buf", ty);
cfg.add(
vartab,
Instr::Set {
loc: *loc,
res: buf,
expr: Expression::AllocDynamicBytes {
loc: *loc,
ty: ty.clone(),
size: Box::new(Expression::NumberLiteral {
loc: *loc,
ty: Type::Uint(32),
value: BigInt::from(0u64),
}),
initializer: Some(vec![]),
},
},
);
Some(Expression::Variable {
loc: *loc,
ty: ty.clone(),
var_no: buf,
})
}
Type::Bytes(_) => Some(Expression::NumberLiteral {
loc: *loc,
ty: ty.clone(),
value: BigInt::from(0u64),
}),
Type::Slice(_) => Some(soroban_vec_new(loc, ty, cfg, vartab)),
Type::Array(elem_ty, dims)
if dims.last() == Some(&ast::ArrayLength::Dynamic)
&& !elem_ty.is_reference_type(ns) =>
{
Some(soroban_vec_new(loc, ty, cfg, vartab))
}
_ => None,
}
}
fn abi_encode(
&self,
loc: &pt::Loc,
args: Vec<Expression>,
ns: &Namespace,
vartab: &mut Vartable,
cfg: &mut ControlFlowGraph,
packed: bool,
) -> (Expression, Expression) {
let (buffer, size, _) = soroban_encode(loc, args, ns, vartab, cfg, packed);
(buffer, size)
}
fn abi_decode(
&self,
loc: &pt::Loc,
buffer: &Expression,
types: &[Type],
ns: &Namespace,
vartab: &mut Vartable,
cfg: &mut ControlFlowGraph,
buffer_size_expr: Option<Expression>,
) -> Vec<Expression> {
soroban_decode(loc, buffer, types, ns, vartab, cfg, buffer_size_expr)
}
fn storage_array_push(
&self,
loc: &pt::Loc,
args: &[ast::Expression],
cfg: &mut ControlFlowGraph,
contract_no: usize,
func: Option<&Function>,
ns: &Namespace,
vartab: &mut Vartable,
opt: &Options,
) -> Expression {
if args[0].ty().is_storage_bytes() {
return soroban_bytes_push(loc, args, cfg, contract_no, func, ns, vartab, opt, self);
}
let elem_is_ref = matches!(
args[0].ty(),
Type::StorageRef(_, inner)
if matches!(inner.deref_any(), Type::Array(elem_ty, _)
if elem_ty.is_reference_type(ns))
);
if elem_is_ref {
storage_slots_array_push(loc, args, cfg, contract_no, func, ns, vartab, opt, self)
} else {
soroban_storage_push(loc, args, cfg, contract_no, func, ns, vartab, opt, self)
}
}
fn storage_array_pop(
&self,
loc: &pt::Loc,
args: &[ast::Expression],
return_ty: &Type,
cfg: &mut ControlFlowGraph,
contract_no: usize,
func: Option<&Function>,
ns: &Namespace,
vartab: &mut Vartable,
opt: &Options,
) -> Expression {
if args[0].ty().is_storage_bytes() {
soroban_bytes_pop(
loc,
args,
return_ty,
cfg,
contract_no,
func,
ns,
vartab,
opt,
self,
)
} else {
soroban_storage_pop(
loc,
args,
return_ty,
cfg,
contract_no,
func,
ns,
vartab,
opt,
self,
)
}
}
fn storage_array_entry_offset(
&self,
loc: &pt::Loc,
var_expr: &Expression,
index: Expression,
elem_ty: &Type,
_slot_ty: &Type,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
ns: &Namespace,
) -> Expression {
let index_encoded = soroban_encode_arg(index, cfg, vartab, ns);
Expression::Subscript {
loc: *loc,
ty: elem_ty.clone(),
array_ty: Type::StorageRef(false, Box::new(elem_ty.clone())),
expr: Box::new(var_expr.clone()),
index: Box::new(index_encoded),
}
}
fn event_emitter<'a>(
&self,
_loc: &pt::Loc,
event_no: usize,
args: &'a [ast::Expression],
ns: &'a Namespace,
) -> Box<dyn EventEmitter + 'a> {
Box::new(SorobanEventEmitter { args, ns, event_no })
}
fn lower_builtin(
&self,
loc: &pt::Loc,
builtin: ast::Builtin,
args: &[ast::Expression],
cfg: &mut ControlFlowGraph,
contract_no: usize,
func: Option<&Function>,
ns: &Namespace,
vartab: &mut Vartable,
opt: &Options,
) -> Option<Expression> {
match builtin {
ast::Builtin::GetAddress => {
if let Some(constant_id) = &ns.contracts[contract_no].program_id {
return Some(Expression::NumberLiteral {
loc: *loc,
ty: Type::Address(false),
value: BigInt::from_bytes_be(Sign::Plus, constant_id),
});
}
let address_var_no = vartab.temp_anonymous(&Type::Uint(64));
let address_var = Expression::Variable {
loc: *loc,
ty: Type::Address(false),
var_no: address_var_no,
};
cfg.add(
vartab,
Instr::Call {
res: vec![address_var_no],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::GetCurrentContractAddress.name().to_string(),
},
args: vec![],
},
);
Some(address_var)
}
ast::Builtin::RequireAuth => {
let var_temp = vartab.temp(
&pt::Identifier {
name: "auth".to_owned(),
loc: *loc,
},
&Type::Bool,
);
let var = Expression::Variable {
loc: *loc,
ty: Type::Address(false),
var_no: var_temp,
};
let expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, self);
let expr = if let Type::StorageRef(_, _) = args[0].ty() {
let expr_no = vartab.temp_anonymous(&Type::Address(false));
let expr = Expression::Variable {
loc: pt::Loc::Codegen,
ty: Type::Address(false),
var_no: expr_no,
};
let storage_load = Instr::LoadStorage {
res: expr_no,
ty: Type::Address(false),
storage: expr.clone(),
storage_type: None,
};
cfg.add(vartab, storage_load);
expr
} else {
expr
};
let instr = Instr::Call {
res: vec![var_temp],
return_tys: vec![Type::Void],
call: InternalCallTy::HostFunction {
name: HostFunctions::RequireAuth.name().to_string(),
},
args: vec![expr],
};
cfg.add(vartab, instr);
Some(var)
}
ast::Builtin::AuthAsCurrContract => {
let symbol_key_1 = Expression::BytesLiteral {
loc: pt::Loc::Codegen,
ty: Type::String,
value: "contract".as_bytes().to_vec(),
};
let symbol_key_2 = Expression::BytesLiteral {
loc: pt::Loc::Codegen,
ty: Type::String,
value: "fn_name".as_bytes().to_vec(),
};
let symbol_key_3 = Expression::BytesLiteral {
loc: pt::Loc::Codegen,
ty: Type::String,
value: "args".as_bytes().to_vec(),
};
let symbols = soroban_encode(
loc,
vec![symbol_key_1, symbol_key_2, symbol_key_3],
ns,
vartab,
cfg,
false,
)
.2;
let contract_value =
expression(&args[0], cfg, contract_no, func, ns, vartab, opt, self);
let fn_name_symbol =
expression(&args[1], cfg, contract_no, func, ns, vartab, opt, self);
let symbol_string =
if let Expression::BytesLiteral { loc, ty: _, value } = fn_name_symbol {
Expression::BytesLiteral {
loc,
ty: Type::String,
value,
}
} else {
unreachable!()
};
let encode_func_symbol =
soroban_encode(loc, vec![symbol_string], ns, vartab, cfg, false).2[0].clone();
let mut args_vec = Vec::new();
for arg in args.iter().skip(2) {
let arg = expression(arg, cfg, contract_no, func, ns, vartab, opt, self);
args_vec.push(arg);
}
let args_encoded = self.abi_encode(loc, args_vec.clone(), ns, vartab, cfg, false);
let args_buf = args_encoded.0;
let args_buf_ptr = Expression::VectorData {
pointer: Box::new(args_buf.clone()),
};
let args_buf_extended = Expression::ZeroExt {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
expr: Box::new(args_buf_ptr.clone()),
};
let args_buf_shifted = Expression::ShiftLeft {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
left: Box::new(args_buf_extended.clone()),
right: Box::new(Expression::NumberLiteral {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
value: BigInt::from(32),
}),
};
let args_buf_pos = Expression::Add {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
left: Box::new(args_buf_shifted.clone()),
right: Box::new(Expression::NumberLiteral {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
value: BigInt::from(4),
}),
overflowing: false,
};
let args_len = Expression::NumberLiteral {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
value: BigInt::from(args_vec.len()),
};
let args_len_encoded = Expression::ShiftLeft {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
left: Box::new(args_len.clone()),
right: Box::new(Expression::NumberLiteral {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
value: BigInt::from(32),
}),
};
let args_len_encoded = Expression::Add {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
left: Box::new(args_len_encoded.clone()),
right: Box::new(Expression::NumberLiteral {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
value: BigInt::from(4),
}),
overflowing: false,
};
let args_vec_var_no = vartab.temp_anonymous(&Type::Uint(64));
let args_vec_var = Expression::Variable {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
var_no: args_vec_var_no,
};
let vec_new_from_linear_mem = Instr::Call {
res: vec![args_vec_var_no],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::VectorNewFromLinearMemory.name().to_string(),
},
args: vec![args_buf_pos.clone(), args_len_encoded],
};
cfg.add(vartab, vec_new_from_linear_mem);
let context_map = vartab.temp_anonymous(&Type::Uint(64));
let context_map_var = Expression::Variable {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
var_no: context_map,
};
let context_map_new = Instr::Call {
res: vec![context_map],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::MapNew.name().to_string(),
},
args: vec![],
};
cfg.add(vartab, context_map_new);
let context_map_put = Instr::Call {
res: vec![context_map],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::MapPut.name().to_string(),
},
args: vec![context_map_var.clone(), symbols[0].clone(), contract_value],
};
cfg.add(vartab, context_map_put);
let context_map_put_2 = Instr::Call {
res: vec![context_map],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::MapPut.name().to_string(),
},
args: vec![
context_map_var.clone(),
symbols[1].clone(),
encode_func_symbol,
],
};
cfg.add(vartab, context_map_put_2);
let context_map_put_3 = Instr::Call {
res: vec![context_map],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::MapPut.name().to_string(),
},
args: vec![
context_map_var.clone(),
symbols[2].clone(),
args_vec_var.clone(),
],
};
cfg.add(vartab, context_map_put_3);
let key_1 = Expression::BytesLiteral {
loc: pt::Loc::Codegen,
ty: Type::String,
value: "context".as_bytes().to_vec(),
};
let key_2 = Expression::BytesLiteral {
loc: pt::Loc::Codegen,
ty: Type::String,
value: "sub_invocations".as_bytes().to_vec(),
};
let keys = soroban_encode(loc, vec![key_1, key_2], ns, vartab, cfg, false).2;
let sub_invocations_map = vartab.temp_anonymous(&Type::Uint(64));
let sub_invocations_map_var = Expression::Variable {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
var_no: sub_invocations_map,
};
let sub_invocations_map_new = Instr::Call {
res: vec![sub_invocations_map],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::MapNew.name().to_string(),
},
args: vec![],
};
cfg.add(vartab, sub_invocations_map_new);
let sub_invocations_map_put = Instr::Call {
res: vec![sub_invocations_map],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::MapPut.name().to_string(),
},
args: vec![
sub_invocations_map_var.clone(),
keys[0].clone(),
context_map_var,
],
};
cfg.add(vartab, sub_invocations_map_put);
let empy_vec_var = vartab.temp_anonymous(&Type::Uint(64));
let empty_vec_expr = Expression::Variable {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
var_no: empy_vec_var,
};
let empty_vec = Instr::Call {
res: vec![empy_vec_var],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::VectorNew.name().to_string(),
},
args: vec![],
};
cfg.add(vartab, empty_vec);
let sub_invocations_map_put_2 = Instr::Call {
res: vec![sub_invocations_map],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::MapPut.name().to_string(),
},
args: vec![
sub_invocations_map_var.clone(),
keys[1].clone(),
empty_vec_expr,
],
};
cfg.add(vartab, sub_invocations_map_put_2);
let contract_capitalized = Expression::BytesLiteral {
loc: pt::Loc::Codegen,
ty: Type::String,
value: "Contract".as_bytes().to_vec(),
};
let contract_capitalized =
soroban_encode(loc, vec![contract_capitalized], ns, vartab, cfg, false).2[0]
.clone();
let enum_vec = vartab.temp_anonymous(&Type::Uint(64));
let enum_vec_var = Expression::Variable {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
var_no: enum_vec,
};
let enum_vec_new = Instr::Call {
res: vec![enum_vec],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::VectorNew.name().to_string(),
},
args: vec![],
};
cfg.add(vartab, enum_vec_new);
let enum_vec_put = Instr::Call {
res: vec![enum_vec],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::VecPushBack.name().to_string(),
},
args: vec![enum_vec_var.clone(), contract_capitalized],
};
cfg.add(vartab, enum_vec_put);
let enum_vec_put_2 = Instr::Call {
res: vec![enum_vec],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::VecPushBack.name().to_string(),
},
args: vec![enum_vec_var.clone(), sub_invocations_map_var],
};
cfg.add(vartab, enum_vec_put_2);
let vec = vartab.temp_anonymous(&Type::Uint(64));
let vec_var = Expression::Variable {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
var_no: vec,
};
let vec_new = Instr::Call {
res: vec![vec],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::VectorNew.name().to_string(),
},
args: vec![],
};
cfg.add(vartab, vec_new);
let vec_push_back = Instr::Call {
res: vec![vec],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::VecPushBack.name().to_string(),
},
args: vec![vec_var.clone(), enum_vec_var],
};
cfg.add(vartab, vec_push_back);
let call_res = vartab.temp_anonymous(&Type::Uint(64));
let call_res_var = Expression::Variable {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
var_no: call_res,
};
let auth_call = Instr::Call {
res: vec![call_res],
return_tys: vec![Type::Void],
call: InternalCallTy::HostFunction {
name: HostFunctions::AuthAsCurrContract.name().to_string(),
},
args: vec![vec_var],
};
cfg.add(vartab, auth_call);
Some(call_res_var)
}
ast::Builtin::ExtendTtl => {
let mut arguments: Vec<Expression> = args
.iter()
.map(|v| expression(v, cfg, contract_no, func, ns, vartab, opt, self))
.collect();
let var_no = match arguments[0].clone() {
Expression::NumberLiteral { value, .. } => value,
_ => panic!("First argument of extendTtl() must be a number literal"),
}
.to_usize()
.expect("Unable to convert var_no to usize");
let var = ns.contracts[contract_no].variables.get(var_no).unwrap();
let storage_type_usize = match var
.storage_type
.clone()
.expect("Unable to get storage type")
{
pt::StorageType::Temporary(_) => 0,
pt::StorageType::Persistent(_) => 1,
pt::StorageType::Instance(_) => panic!(
"Calling extendTtl() on instance storage is not allowed. Use `extendInstanceTtl()` instead."
),
};
arguments.push(Expression::NumberLiteral {
loc: *loc,
ty: Type::Uint(32),
value: BigInt::from(storage_type_usize),
});
Some(Expression::Builtin {
loc: *loc,
tys: vec![Type::Int(64)],
kind: (&builtin).into(),
args: arguments,
})
}
_ => None,
}
}
fn lower_storage_struct_member(
&self,
loc: &pt::Loc,
var_expr: Expression,
struct_ty: &StructType,
field_no: usize,
ns: &Namespace,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
) -> Expression {
let offset: BigInt = struct_ty.definition(ns).fields[..field_no]
.iter()
.filter(|field| !field.infinite_size)
.map(|field| field.ty.storage_slots(ns))
.sum();
let offset_expr = Expression::NumberLiteral {
loc: *loc,
ty: Type::Uint(32),
value: offset,
};
let offset_encoded = soroban_encode_arg(offset_expr, cfg, vartab, ns);
let res = vartab.temp_name("vec_push_codegen", &Type::Uint(64));
cfg.add(
vartab,
Instr::Call {
res: vec![res],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::VecPushBack.name().to_string(),
},
args: vec![var_expr, offset_encoded],
},
);
Expression::Variable {
loc: pt::Loc::Codegen,
ty: Type::Uint(64),
var_no: res,
}
}
fn lower_load_storage(
&self,
value: Expression,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
ns: &Namespace,
) -> Expression {
soroban_decode_arg(value, cfg, vartab, ns, None)
}
fn post_process_program(&self, _ns: &mut Namespace, _opt: &Options) {}
fn selector_hash_algorithm(&self) -> ast::Builtin {
ast::Builtin::Keccak256
}
fn initial_storage_slot(&self) -> BigInt {
BigInt::zero()
}
fn align_storage_slot(&self, slot: BigInt, _ty: &Type, _ns: &Namespace) -> BigInt {
slot
}
fn default_gas_builtin(&self) -> BigInt {
BigInt::zero()
}
fn lower_print_expr(&self, expr: Expression) -> Expression {
expr
}
fn lower_mapping_subscript(
&self,
loc: &pt::Loc,
elem_ty: &Type,
array_ty: &Type,
array: Expression,
index: Expression,
) -> Expression {
Expression::Subscript {
loc: *loc,
ty: elem_ty.clone(),
array_ty: array_ty.clone(),
expr: Box::new(array),
index: Box::new(index),
}
}
}
pub(super) fn validate_accessor_abi_types(contract_no: usize, ns: &mut Namespace) {
for variable in &ns.contracts[contract_no].variables {
if !matches!(variable.visibility, pt::Visibility::Public(_)) {
continue;
}
if let Some(unsupported_type) = unsupported_accessor_type(&variable.ty, ns) {
ns.diagnostics.push(Diagnostic::error(
variable.loc,
format!(
"type '{unsupported_type}' is not supported as a Soroban public variable accessor return value"
),
));
}
}
}
pub(super) fn validate_event_abi_types(contract_no: usize, ns: &mut Namespace) {
for event_no in ns.contracts[contract_no].emits_events.clone() {
for field in &ns.events[event_no].fields {
if let Some(unsupported_type) = unsupported_event_type(&field.ty, ns) {
ns.diagnostics.push(Diagnostic::error(
field.ty_loc.unwrap_or(field.loc),
format!(
"type '{unsupported_type}' is not supported as a Soroban event parameter"
),
));
}
}
}
}
pub(super) fn validate_abi_types(all_cfg: &[ControlFlowGraph], ns: &mut Namespace) {
for cfg in all_cfg {
if !cfg.public {
continue;
}
if is_public_accessor(cfg, ns) {
continue;
}
if cfg.returns.len() > 1 {
let loc = cfg.returns[1].ty_loc.unwrap_or(cfg.returns[1].loc);
ns.diagnostics.push(Diagnostic::error(
loc,
"Soroban external functions can return at most one value".to_string(),
));
continue;
}
for param in cfg.params.as_ref() {
if let Some(unsupported_type) = unsupported_parameter_type(¶m.ty, ns) {
ns.diagnostics.push(Diagnostic::error(
param.ty_loc.unwrap_or(param.loc),
format!(
"type '{unsupported_type}' is not supported as a Soroban external function parameter"
),
));
}
}
for ret in cfg.returns.as_ref() {
if let Some(unsupported_type) = unsupported_return_type(&ret.ty, ns) {
ns.diagnostics.push(Diagnostic::error(
ret.ty_loc.unwrap_or(ret.loc),
format!(
"type '{unsupported_type}' is not supported as a Soroban external function return value"
),
));
}
}
}
validate_unsupported_codegen_paths(all_cfg, ns);
}
fn unsupported_parameter_type(ty: &Type, ns: &Namespace) -> Option<String> {
match ty {
Type::Struct(_) => Some(format!("{} memory", ty.to_string(ns))),
Type::Array(elem, _) if has_unsupported_soroban_array_element(elem.as_ref()) => {
Some(format!("{} memory", ty.to_string(ns)))
}
_ => None,
}
}
fn is_public_accessor(cfg: &ControlFlowGraph, ns: &Namespace) -> bool {
match cfg.function_no {
ASTFunction::SolidityFunction(function_no) => ns.functions[function_no].is_accessor,
_ => false,
}
}
fn unsupported_accessor_type(ty: &Type, ns: &Namespace) -> Option<String> {
match ty {
Type::Mapping(mapping) => unsupported_accessor_type(mapping.value.as_ref(), ns),
Type::Array(elem, _) => unsupported_accessor_type(elem.as_ref(), ns),
Type::Struct(struct_ty) => {
let fields = &struct_ty.definition(ns).fields;
if fields.len() > 1 {
Some(ty.to_string(ns))
} else {
fields
.first()
.and_then(|field| unsupported_accessor_type(&field.ty, ns))
}
}
_ => None,
}
}
fn unsupported_event_type(ty: &Type, ns: &Namespace) -> Option<String> {
match ty {
Type::Struct(_) => Some(ty.to_string(ns)),
_ => None,
}
}
fn unsupported_return_type(ty: &Type, ns: &Namespace) -> Option<String> {
match ty {
Type::Struct(_) => Some(format!("{} memory", ty.to_string(ns))),
Type::Array(_, _) => Some(format!("{} memory", ty.to_string(ns))),
_ => None,
}
}
fn has_unsupported_soroban_array_element(ty: &Type) -> bool {
match ty {
Type::DynamicBytes | Type::Bytes(_) | Type::Struct(_) => true,
Type::Array(elem, _) => has_unsupported_soroban_array_element(elem.as_ref()),
_ => false,
}
}
fn validate_unsupported_codegen_paths(all_cfg: &[ControlFlowGraph], ns: &mut Namespace) {
for cfg in all_cfg {
for block in &cfg.blocks {
for instr in &block.instr {
validate_unsupported_codegen_instr(instr, ns);
}
}
}
}
fn validate_unsupported_codegen_instr(instr: &Instr, ns: &mut Namespace) {
match instr {
Instr::LoadStorage { ty, storage, .. } => {
if let Some(unsupported_type) = unsupported_soroban_storage_type(ty, ns) {
push_codegen_error(
ns,
CodegenError::unsupported_soroban_type(
storage.loc(),
"in storage load",
unsupported_type,
),
);
}
}
Instr::SetStorage { ty, storage, .. } => {
if let Some(unsupported_type) = unsupported_soroban_storage_type(ty, ns) {
push_codegen_error(
ns,
CodegenError::unsupported_soroban_type(
storage.loc(),
"in storage store",
unsupported_type,
),
);
}
}
Instr::PushStorage { ty, storage, .. } => {
if let Some(unsupported_type) = unsupported_soroban_storage_type(ty, ns) {
push_codegen_error(
ns,
CodegenError::unsupported_soroban_type(
storage.loc(),
"in storage push",
unsupported_type,
),
);
}
}
Instr::PopStorage { ty, storage, .. } => {
if let Some(unsupported_type) = unsupported_soroban_storage_type(ty, ns) {
push_codegen_error(
ns,
CodegenError::unsupported_soroban_type(
storage.loc(),
"in storage pop",
unsupported_type,
),
);
}
}
Instr::Constructor { loc, .. } => {
push_codegen_error(
ns,
CodegenError::unsupported_soroban_operation(*loc, "contract construction"),
);
}
Instr::ValueTransfer { address, .. } => {
push_codegen_error(
ns,
CodegenError::unsupported_soroban_operation(address.loc(), "value transfer"),
);
}
Instr::SelfDestruct { recipient } => {
push_codegen_error(
ns,
CodegenError::unsupported_soroban_operation(recipient.loc(), "selfdestruct"),
);
}
_ => (),
}
}
fn unsupported_soroban_storage_type(ty: &Type, ns: &Namespace) -> Option<String> {
match ty.deref_any() {
Type::ExternalFunction { .. } => Some(ty.to_string(ns)),
Type::Array(elem, _) => unsupported_soroban_storage_type(elem.as_ref(), ns),
Type::Mapping(mapping) => unsupported_soroban_storage_type(mapping.value.as_ref(), ns),
Type::Struct(struct_ty) => struct_ty
.definition(ns)
.fields
.iter()
.find_map(|field| unsupported_soroban_storage_type(&field.ty, ns)),
_ => None,
}
}
fn push_codegen_error(ns: &mut Namespace, err: CodegenError) {
if let Some(diagnostic) = err.diagnostic() {
ns.diagnostics.push(diagnostic);
}
}
fn soroban_vec_handle_ty(vec_ty: &Type) -> Type {
let inner_ty = if let Type::StorageRef(_, inner) = vec_ty {
inner.as_ref().clone()
} else {
vec_ty.clone()
};
Type::SorobanHandle(Box::new(inner_ty))
}
pub(crate) fn soroban_vec_new(
loc: &pt::Loc,
vec_ty: &Type,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
) -> Expression {
let handle_ty = soroban_vec_handle_ty(vec_ty);
let empty_vec_no = vartab.temp_name("soroban_vec_new", &handle_ty);
let empty_vec_var = Expression::Variable {
loc: *loc,
ty: handle_ty.clone(),
var_no: empty_vec_no,
};
cfg.add(
vartab,
Instr::Call {
call: InternalCallTy::HostFunction {
name: HostFunctions::VectorNew.name().to_string(),
},
args: vec![],
return_tys: vec![handle_ty],
res: vec![empty_vec_no],
},
);
empty_vec_var
}
fn soroban_vec_push_back(
loc: &pt::Loc,
vec_obj: Expression,
vec_ty: &Type,
value: Expression,
cfg: &mut ControlFlowGraph,
ns: &Namespace,
vartab: &mut Vartable,
) -> Expression {
let value_encoded = soroban_encode_arg(value, cfg, vartab, ns);
let handle_ty = soroban_vec_handle_ty(vec_ty);
let new_vec_no = vartab.temp_name("soroban_vec_push", &handle_ty);
let new_vec_var = Expression::Variable {
loc: *loc,
ty: handle_ty.clone(),
var_no: new_vec_no,
};
let instr = Instr::Call {
res: vec![new_vec_no],
return_tys: vec![handle_ty],
call: InternalCallTy::HostFunction {
name: HostFunctions::VecPushBack.name().to_string(),
},
args: vec![vec_obj, value_encoded],
};
cfg.add(vartab, instr);
new_vec_var
}
fn soroban_vec_pop_back(
loc: &pt::Loc,
vec_obj: Expression,
vec_ty: &Type,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
) -> Expression {
let handle_ty = soroban_vec_handle_ty(vec_ty);
let new_vec_no = vartab.temp_name("soroban_vec_pop", &handle_ty);
let new_vec_var = Expression::Variable {
loc: *loc,
ty: handle_ty.clone(),
var_no: new_vec_no,
};
let instr = Instr::Call {
res: vec![new_vec_no],
return_tys: vec![handle_ty],
call: InternalCallTy::HostFunction {
name: HostFunctions::VecPopBack.name().to_string(),
},
args: vec![vec_obj],
};
cfg.add(vartab, instr);
new_vec_var
}
pub(crate) fn soroban_storage_push(
loc: &pt::Loc,
args: &[ast::Expression],
cfg: &mut ControlFlowGraph,
contract_no: usize,
func: Option<&Function>,
ns: &Namespace,
vartab: &mut Vartable,
opt: &Options,
target: &dyn TargetCodegen,
) -> Expression {
let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target);
let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target);
let vec_ty = args[0].ty();
let old_vec_obj = load_storage(
loc,
&vec_ty,
var_expr.clone(),
cfg,
vartab,
None,
ns,
target,
);
let new_vec_var = soroban_vec_push_back(loc, old_vec_obj, &vec_ty, value, cfg, ns, vartab);
let store_instr = Instr::SetStorage {
ty: vec_ty,
value: new_vec_var.clone(),
storage: var_expr.clone(),
storage_type: None,
};
cfg.add(vartab, store_instr);
var_expr
}
pub(crate) fn soroban_storage_pop(
loc: &pt::Loc,
args: &[ast::Expression],
return_ty: &Type,
cfg: &mut ControlFlowGraph,
contract_no: usize,
func: Option<&Function>,
ns: &Namespace,
vartab: &mut Vartable,
opt: &Options,
target: &dyn TargetCodegen,
) -> Expression {
let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target);
let vec_ty = args[0].ty();
let old_vec_obj = load_storage(
loc,
&vec_ty,
var_expr.clone(),
cfg,
vartab,
None,
ns,
target,
);
let new_vec_var = soroban_vec_pop_back(loc, old_vec_obj, &vec_ty, cfg, vartab);
let new_vec_no = match &new_vec_var {
Expression::Variable { var_no, .. } => *var_no,
_ => unreachable!(),
};
let store_instr = Instr::SetStorage {
ty: vec_ty,
value: new_vec_var.clone(),
storage: var_expr.clone(),
storage_type: None,
};
cfg.add(vartab, store_instr);
Expression::Variable {
loc: *loc,
ty: return_ty.clone(),
var_no: new_vec_no,
}
}
pub(crate) fn soroban_bytes_push(
loc: &pt::Loc,
args: &[ast::Expression],
cfg: &mut ControlFlowGraph,
contract_no: usize,
func: Option<&Function>,
ns: &Namespace,
vartab: &mut Vartable,
opt: &Options,
target: &dyn TargetCodegen,
) -> Expression {
let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target);
let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt, target);
let bytes_ty = args[0].ty();
let handle = soroban_load_storage_handle(loc, var_expr.clone(), cfg, vartab);
let byte_u32 = value.cast(&Type::Uint(8), ns).cast(&Type::Uint(32), ns);
let value_encoded = soroban_encode_arg(byte_u32, cfg, vartab, ns);
let new_no = vartab.temp_name("bytes_push", &Type::Uint(64));
cfg.add(
vartab,
Instr::Call {
res: vec![new_no],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::BytesPush.name().to_string(),
},
args: vec![handle, value_encoded],
},
);
let new_handle = Expression::Variable {
loc: *loc,
ty: Type::Uint(64),
var_no: new_no,
};
cfg.add(
vartab,
Instr::SetStorage {
ty: bytes_ty,
value: new_handle,
storage: var_expr.clone(),
storage_type: None,
},
);
var_expr
}
pub(crate) fn soroban_bytes_pop(
loc: &pt::Loc,
args: &[ast::Expression],
return_ty: &Type,
cfg: &mut ControlFlowGraph,
contract_no: usize,
func: Option<&Function>,
ns: &Namespace,
vartab: &mut Vartable,
opt: &Options,
target: &dyn TargetCodegen,
) -> Expression {
let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt, target);
let bytes_ty = args[0].ty();
let handle = soroban_load_storage_handle(loc, var_expr.clone(), cfg, vartab);
let new_no = vartab.temp_name("bytes_pop", &Type::Uint(64));
cfg.add(
vartab,
Instr::Call {
res: vec![new_no],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::BytesPop.name().to_string(),
},
args: vec![handle],
},
);
let new_handle = Expression::Variable {
loc: *loc,
ty: Type::Uint(64),
var_no: new_no,
};
cfg.add(
vartab,
Instr::SetStorage {
ty: bytes_ty,
value: new_handle,
storage: var_expr,
storage_type: None,
},
);
Expression::Undefined {
ty: return_ty.clone(),
}
}
pub(crate) fn soroban_bytes_length(
loc: &pt::Loc,
bytes_var: Expression,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
ns: &Namespace,
) -> Expression {
let bytes_handle = soroban_load_storage_handle(loc, bytes_var, cfg, vartab);
let var_no = vartab.temp_name("bytes_obj_length", &Type::Uint(64));
let var = Expression::Variable {
loc: *loc,
ty: Type::Uint(64),
var_no,
};
cfg.add(
vartab,
Instr::Call {
res: vec![var_no],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::BytesLen.name().to_string(),
},
args: vec![bytes_handle],
},
);
soroban_decode_arg(var, cfg, vartab, ns, Some(Type::Uint(32)))
}
pub(crate) fn soroban_strings_length(
loc: &pt::Loc,
bytes_var: Expression,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
ns: &Namespace,
) -> Expression {
let string_handle = soroban_load_storage_handle(loc, bytes_var, cfg, vartab);
let var_no = vartab.temp_name("string_obj_length", &Type::Uint(64));
let var = Expression::Variable {
loc: *loc,
ty: Type::Uint(64),
var_no,
};
cfg.add(
vartab,
Instr::Call {
res: vec![var_no],
return_tys: vec![Type::Uint(64)],
call: InternalCallTy::HostFunction {
name: HostFunctions::StringLen.name().to_string(),
},
args: vec![string_handle],
},
);
soroban_decode_arg(var, cfg, vartab, ns, Some(Type::Uint(32)))
}
pub(crate) fn soroban_bytes_new(
loc: &pt::Loc,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
) -> Expression {
let ty = Type::SorobanHandle(Box::new(Type::DynamicBytes));
let bytes_no = vartab.temp_name("bytes_obj_new", &ty);
cfg.add(
vartab,
Instr::Call {
res: vec![bytes_no],
return_tys: vec![ty.clone()],
call: InternalCallTy::HostFunction {
name: HostFunctions::BytesNew.name().to_string(),
},
args: vec![],
},
);
Expression::Variable {
loc: *loc,
ty,
var_no: bytes_no,
}
}
fn soroban_load_storage_handle(
loc: &pt::Loc,
storage: Expression,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
) -> Expression {
let handle_no = vartab.temp_name("storage_handle", &Type::Uint(64));
cfg.add(
vartab,
Instr::LoadStorage {
res: handle_no,
ty: Type::Uint(64),
storage,
storage_type: None,
},
);
Expression::Variable {
loc: *loc,
ty: Type::Uint(64),
var_no: handle_no,
}
}