use bimap::BiMap;
use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
use num_bigint::BigInt;
use test_case::test_case;
use super::LibfuncSimulationError::{
self, FunctionSimulationError, WrongArgType, WrongNumberOfArgs,
};
use super::value::CoreValue::{
self, Array, Felt252, GasBuiltin, RangeCheck, U128MulGuarantee, Uint32, Uint64, Uint128,
Uninitialized,
};
use super::{SimulationError, core};
use crate::extensions::GenericLibfunc;
use crate::extensions::core::CoreLibfunc;
use crate::extensions::lib_func::{SignatureSpecializationContext, SpecializationContext};
use crate::extensions::type_specialization_context::TypeSpecializationContext;
use crate::extensions::types::TypeInfo;
use crate::ids::{ConcreteTypeId, FunctionId, GenericTypeId};
use crate::program::{ConcreteTypeLongId, Function, FunctionSignature, GenericArg, StatementIdx};
use crate::test_utils::build_bijective_mapping;
fn type_arg(name: &str) -> GenericArg {
GenericArg::Type(name.into())
}
fn value_arg(v: i64) -> GenericArg {
GenericArg::Value(BigInt::from(v))
}
fn user_func_arg(name: &str) -> GenericArg {
GenericArg::UserFunc(name.into())
}
struct MockSpecializationContext {
mapping: BiMap<ConcreteTypeId, ConcreteTypeLongId>,
type_infos: UnorderedHashMap<ConcreteTypeId, TypeInfo>,
}
impl MockSpecializationContext {
pub fn new() -> Self {
let mapping = build_bijective_mapping();
let mut type_infos = UnorderedHashMap::<ConcreteTypeId, TypeInfo>::default();
for name in ["u128", "u64"] {
let key = name.into();
let long_id = mapping.get_by_left(&key).unwrap().clone();
type_infos.insert(
key,
TypeInfo {
long_id,
storable: true,
droppable: true,
duplicatable: true,
zero_sized: false,
},
);
}
let key = "ArrayU128".into();
let long_id = mapping.get_by_left(&key).unwrap().clone();
type_infos.insert(
key,
TypeInfo {
long_id,
storable: true,
droppable: true,
duplicatable: false,
zero_sized: false,
},
);
Self { mapping, type_infos }
}
}
impl SpecializationContext for MockSpecializationContext {
fn try_get_function(&self, function_id: &FunctionId) -> Option<Function> {
["drop_all_inputs", "identity", "unimplemented"]
.into_iter()
.map(|name| -> FunctionId { name.into() })
.find(|id: &FunctionId| id == function_id)
.map(|_| Function::new(function_id.clone(), vec![], vec![], StatementIdx(0)))
}
}
impl TypeSpecializationContext for MockSpecializationContext {
fn try_get_type_info(&self, id: &ConcreteTypeId) -> Option<&TypeInfo> {
self.type_infos.get(id)
}
}
impl SignatureSpecializationContext for MockSpecializationContext {
fn try_get_concrete_type(
&self,
id: GenericTypeId,
generic_args: &[GenericArg],
) -> Option<ConcreteTypeId> {
self.mapping
.get_by_right(&ConcreteTypeLongId {
generic_id: id,
generic_args: generic_args.to_vec(),
})
.cloned()
}
fn try_get_function_signature(&self, function_id: &FunctionId) -> Option<FunctionSignature> {
self.try_get_function(function_id).map(|f| f.signature)
}
}
fn simulate(
id: &str,
generic_args: Vec<GenericArg>,
inputs: Vec<CoreValue>,
) -> Result<(Vec<CoreValue>, usize), LibfuncSimulationError> {
core::simulate(
&CoreLibfunc::by_id(&id.into())
.unwrap()
.specialize(&MockSpecializationContext::new(), &generic_args)
.unwrap(),
inputs,
|| Some(4),
|id, inputs| {
if id == &"drop_all_inputs".into() {
Ok(vec![])
} else if id == &"identity".into() {
Ok(inputs)
} else {
Err(FunctionSimulationError(
id.clone(),
Box::new(SimulationError::StatementOutOfBounds(StatementIdx(0))),
))
}
},
)
}
#[test_case("withdraw_gas", vec![], vec![RangeCheck, GasBuiltin(5)]
=> Ok((vec![RangeCheck, GasBuiltin(1)], 0)); "withdraw_gas(5)")]
#[test_case("withdraw_gas", vec![], vec![RangeCheck, GasBuiltin(2)]
=> Ok((vec![RangeCheck, GasBuiltin(2)], 1)); "withdraw_gas(2)")]
#[test_case("u128_is_zero", vec![], vec![Uint128(2)]
=> Ok((vec![Uint128(2)], 1)); "u128_is_zero(2)")]
#[test_case("u128_is_zero", vec![], vec![Uint128(0)] => Ok((vec![], 0)); "u128_is_zero(0)")]
#[test_case("jump", vec![], vec![] => Ok((vec![], 0)); "jump()")]
#[test_case("u128_overflowing_add", vec![], vec![RangeCheck, Uint128(2), Uint128(3)]
=> Ok((vec![RangeCheck, Uint128(5)], 0));
"u128_overflowing_add(2, 3)")]
#[test_case("u128_overflowing_sub", vec![], vec![RangeCheck, Uint128(5), Uint128(3)]
=> Ok((vec![RangeCheck, Uint128(2)], 0));
"u128_overflowing_sub(5, 3)")]
#[test_case("u128_overflowing_sub", vec![], vec![RangeCheck, Uint128(3), Uint128(5)]
=> Ok((vec![RangeCheck, Uint128(u128::MAX - 1)], 1));
"u128_overflowing_sub(3, 5)")]
#[test_case("u128s_from_felt252", vec![], vec![RangeCheck, Felt252(5u64.into())]
=> Ok((vec![RangeCheck, Uint128(5)], 0)); "u128s_from_felt252(5)")]
#[test_case("u128s_from_felt252", vec![],
vec![RangeCheck, Felt252(((BigInt::from(3) << 128_u32) + BigInt::from(7)).into())]
=> Ok((vec![RangeCheck, Uint128(3), Uint128(7)], 1));
"u128s_from_felt252(3 * 2**128 + 7)")]
fn simulate_branch(
id: &str,
generic_args: Vec<GenericArg>,
inputs: Vec<CoreValue>,
) -> Result<(Vec<CoreValue>, usize), LibfuncSimulationError> {
simulate(id, generic_args, inputs)
}
#[test_case("redeposit_gas", vec![], vec![GasBuiltin(2)] => Ok(vec![GasBuiltin(6)]); "redeposit_gas(2)")]
#[test_case("array_new", vec![type_arg("u128")], vec![] => Ok(vec![Array(vec![])]); "array_new()")]
#[test_case("array_append", vec![type_arg("u128")], vec![Array(vec![]), Uint128(4)] =>
Ok(vec![Array(vec![Uint128(4)])]); "array_append([], 4)")]
#[test_case("array_get", vec![type_arg("u128")], vec![RangeCheck, Array(vec![Uint128(5)]), Uint32(0)]
=> Ok(vec![RangeCheck, Uint128(5)]); "array_get([5], 0)")]
#[test_case("array_len", vec![type_arg("u128")], vec![Array(vec![])] =>
Ok(vec![Uint32(0)]); "array_len([])")]
#[test_case("u128_safe_divmod", vec![], vec![RangeCheck, Uint128(32), Uint128(5)]
=> Ok(vec![RangeCheck, Uint128(6), Uint128(2)]); "u128_safe_divmod(32, 5)")]
#[test_case("u128_const", vec![value_arg(3)], vec![] => Ok(vec![Uint128(3)]);
"u128_const<3>()")]
#[test_case("dup", vec![type_arg("u128")], vec![Uint128(24)]
=> Ok(vec![Uint128(24), Uint128(24)]); "dup<u128>(24)")]
#[test_case("drop", vec![type_arg("u128")], vec![Uint128(2)] => Ok(vec![]); "drop<u128>(2)")]
#[test_case("unwrap_non_zero", vec![type_arg("u128")], vec![Uint128(6)]
=> Ok(vec![Uint128(6)]); "unwrap_non_zero<u128>(6)")]
#[test_case("store_temp", vec![type_arg("u128")], vec![Uint128(6)] => Ok(vec![Uint128(6)]);
"store_temp<u128>(6)")]
#[test_case("store_local", vec![type_arg("u128")], vec![Uninitialized, Uint128(6)]
=> Ok(vec![Uint128(6)]); "store_local<u128>(_, 6)")]
#[test_case("finalize_locals", vec![], vec![] => Ok(vec![]); "finalize_locals()")]
#[test_case("rename", vec![type_arg("u128")], vec![Uint128(6)] => Ok(vec![Uint128(6)]);
"rename<u128>(6)")]
#[test_case("function_call", vec![user_func_arg("drop_all_inputs")], vec![Uint128(3), Uint128(5)]
=> Ok(vec![]); "function_call<drop_all_inputs>()")]
#[test_case("function_call", vec![user_func_arg("identity")], vec![Uint128(3), Uint128(5)]
=> Ok(vec![Uint128(3), Uint128(5)]); "function_call<identity>()")]
#[test_case("u64_to_felt252", vec![], vec![Uint64(5)]
=> Ok(vec![Felt252(5u64.into())]); "u64_to_felt252(5)")]
#[test_case("u128_guarantee_mul", vec![], vec![Uint128(2), Uint128(3)]
=> Ok(vec![Uint128(0), Uint128(6), U128MulGuarantee]); "u128_guarantee_mul(2, 3)")]
#[test_case("u128_guarantee_mul", vec![], vec![Uint128(1 << 64), Uint128(1 << 64)]
=> Ok(vec![Uint128(1), Uint128(0), U128MulGuarantee]);
"u128_guarantee_mul(2**64, 2**64) -> high limb")]
fn simulate_none_branch(
id: &str,
generic_args: Vec<GenericArg>,
inputs: Vec<CoreValue>,
) -> Result<Vec<CoreValue>, LibfuncSimulationError> {
simulate(id, generic_args, inputs).map(|(outputs, chosen_branch)| {
assert_eq!(chosen_branch, 0);
outputs
})
}
#[test_case("withdraw_gas", vec![], vec![RangeCheck, Uninitialized] => WrongArgType;
"withdraw_gas(empty)")]
#[test_case("withdraw_gas", vec![], vec![] => WrongNumberOfArgs; "withdraw_gas()")]
#[test_case("redeposit_gas", vec![], vec![Uninitialized] => WrongArgType;
"redeposit_gas(empty)")]
#[test_case("redeposit_gas", vec![], vec![] => WrongNumberOfArgs; "redeposit_gas()")]
#[test_case("u128_overflowing_add", vec![], vec![RangeCheck, Uint128(1)] => WrongNumberOfArgs;
"u128_overflowing_add(1)")]
#[test_case("u128_overflowing_sub", vec![], vec![RangeCheck, Uint128(1)] => WrongNumberOfArgs;
"u128_overflowing_sub(1)")]
#[test_case("u128_safe_divmod", vec![], vec![RangeCheck, Uint128(1)] => WrongNumberOfArgs;
"u128_safe_divmod(1)")]
#[test_case("u128_const", vec![value_arg(3)], vec![Uint128(1)] => WrongNumberOfArgs;
"u128_const<3>(1)")]
#[test_case("dup", vec![type_arg("u128")], vec![] => WrongNumberOfArgs; "dup<u128>()")]
#[test_case("drop", vec![type_arg("u128")], vec![] => WrongNumberOfArgs; "drop<u128>()")]
#[test_case("u128_is_zero", vec![], vec![] => WrongNumberOfArgs; "u128_is_zero()")]
#[test_case("store_temp", vec![type_arg("u128")], vec![] => WrongNumberOfArgs;
"store_temp<u128>()")]
#[test_case("store_local", vec![type_arg("u128")], vec![] => WrongNumberOfArgs;
"store_local<u128>()")]
#[test_case("finalize_locals", vec![], vec![Uint128(4)] => WrongNumberOfArgs; "finalize_locals(4)")]
#[test_case("rename", vec![type_arg("u128")], vec![] => WrongNumberOfArgs; "rename<u128>()")]
#[test_case("jump", vec![], vec![Uint128(4)] => WrongNumberOfArgs; "jump(4)")]
#[test_case("u64_to_felt252", vec![], vec![RangeCheck, Uint64(1)] => WrongNumberOfArgs;
"u64_to_felt252(RangeCheck, 1)")]
#[test_case("function_call", vec![user_func_arg("unimplemented")], vec![] =>
FunctionSimulationError(
"unimplemented".into(),
Box::new(SimulationError::StatementOutOfBounds(StatementIdx(0))));
"function_call<unimplemented>()")]
fn simulate_error(
id: &str,
generic_args: Vec<GenericArg>,
inputs: Vec<CoreValue>,
) -> LibfuncSimulationError {
simulate(id, generic_args, inputs).err().unwrap()
}