Skip to content

Commit

Permalink
feat: Move missing cairo-1 hints from starknet_in_rust repo (lambdacl…
Browse files Browse the repository at this point in the history
…ass#1143)

* Implement TryFrom<CasmContractClass> for Program

* Add cairo1hintprocessor dependency

* Map hints to pc

* Add Cairo1HintProcessor

* Move cairo-1-hint-processor to cairo-rs crate

* fixes

* Fix test helper

* Remove allow

* fix test func

* Add builtins to test

* Extract builtins from contract_class

* Add _builtin to builtin names in etrypoint data

* Copy logic from cairo1 contract execution in starknet

* Remove unused code

* Use lower initial_gas value

* Add program segment size argument

* Check return values in run_cairo_1_entrypoint fn

* Remove debug prints

* Add basic fibonacci test

* Add another fibonacci case

* Always verify secure

* Clippy

* Compile casm contracts when running test target

* Remove unwrap from cell_ref_to_relocatable

* Remove paniking macro from extract_buffer

* Misc improvements

* Misc improvements

* Misc improvements

* Misc improvements

* Remove unwraps & asserts from DictSquashExecScope::pop_current_key

* Remove unwraps & asserts from DictManagerExecScope::new_default_dict

* Remove expect from get_dict_tracker

* Add constants for cairo 1 compiler binaries in makefile

* Add cairo 1 compiler to deps target in makefile

* Add cairo folder to clean target

* Remove todo from execute method

* Separate helper functions from Cairo1HintProcessor implementation

* Add untracked file

* Fix

* Add changelog entry

* Add a job to compile cairo 1 contracts in ci

* Add job dependency

* Fix yml syntax

* Fix yml syntax

* Temporarily extempt cairo-1-hint-processor from codecov

* Fix yml syntax

* Fix workflow

* Remove cache code from new job

* Fix yml syntax

* Fix wrong path

* Fix makefile

* Build only compiler binaries

* Add cairo-1-contracts-cache

* Fetch contracts cache in jobs that need them

* Use no-std version of HashMap

* Import stdlib::prelude::*

* Wrap print in not-wasm block

* Remove std path

* use slices instead of vec

* Make DictSquashExecScope fields private

* Import hint processor dependencies without default features

* -Clippy

* Add type

* Compile cairo 1 contracts in build-programs job

* Rename cache

* Use target dependency instead of explicit $(MAKE)

* Fix yml syntax

* Check for cairo folder before cloning cairo repo

* Ommit folder name

* Swap paths

* Add cairo-1-hints feature flag

* Add compile-hint feature to tests run in workflow

* Add cairo-1-hints to test_utils

* Add cairo-1-hints to test_utils

* Use both paths when fetching compiled test data

* Remove cairo-1-hints feature from test_utils feature

* Move dependencies to cairo-1-hints feature

* Update CHANGELOG.md

* Move Felt252DictEntryInit hint impl

* Add hint Felt252DictEntryUpdate

* Add hint GetCurrentAccessDelta

* Add hint InitSquashData

* Add hint AllocConstantSize

* Add hint GetCurrentAccessIndex

* Add hint Uint256SquareRoot

* Fix cfg directive

* Misc fixes

* Add changelog entry

* Add hint ShouldContinueSquashLoop

* Add hint FieldSqrt

* Fixes

* Add cairo-1-hints to test workflow

* Add Cairo 1 considerations for Gitignore and Makefile (lambdaclass#1144)

* Add ignore for casm and sierra files

* Add libfunc experimental for cairo 1 compilation

* Add new enty to CHANGELOG

* Update CHANGELOG.md

---------

Co-authored-by: Roberto Catalan <robertitocatalan@gmail.com>
  • Loading branch information
2 people authored and kariy committed Jun 23, 2023
1 parent b41dec6 commit 22cb518
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 1 deletion.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

#### Upcoming Changes

* Add more hints to `Cairo1HintProcessor` [#1143](https://github.com/lambdaclass/cairo-rs/pull/1098)

* `Cairo1HintProcessor` can now run the following hints:
* Felt252DictEntryInit
* Felt252DictEntryUpdate
* GetCurrentAccessDelta
* InitSquashData
* AllocConstantSize
* GetCurrentAccessIndex
* ShouldContinueSquashLoop
* FieldSqrt

* Add a test for the `DivMod` hint [#1138](https://github.com/lambdaclass/cairo-rs/pull/1138).

* Add some small considerations regarding Cairo 1 programs [#1144](https://github.com/lambdaclass/cairo-rs/pull/1144):
Expand Down
2 changes: 1 addition & 1 deletion src/hint_processor/cairo_1_hint_processor/dict_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ pub struct DictSquashExecScope {
/// A map from key to the list of indices accessing it, each list in reverse order.
pub(crate) access_indices: HashMap<Felt252, Vec<Felt252>>,
/// Descending list of keys.
keys: Vec<Felt252>,
pub(crate) keys: Vec<Felt252>,
}

impl DictSquashExecScope {
Expand Down
264 changes: 264 additions & 0 deletions src/hint_processor/cairo_1_hint_processor/hint_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::any_box;
use crate::felt::{felt_str, Felt252};
use crate::hint_processor::cairo_1_hint_processor::dict_manager::DictSquashExecScope;
use crate::hint_processor::hint_processor_definition::HintReference;
use crate::types::relocatable::Relocatable;

use crate::stdlib::collections::HashMap;
use crate::stdlib::prelude::*;
Expand All @@ -26,6 +27,12 @@ use num_bigint::BigUint;
use num_integer::Integer;
use num_traits::{cast::ToPrimitive, Zero};

/// Execution scope for constant memory allocation.
struct MemoryExecScope {
/// The first free address in the segment.
next_address: Relocatable,
}

#[derive(MontConfig)]
#[modulus = "3618502788666131213697322783095070105623107215331596699973092056135872020481"]
#[generator = "3"]
Expand Down Expand Up @@ -174,6 +181,31 @@ impl Cairo1HintProcessor {
Hint::Core(CoreHint::ShouldSkipSquashLoop { should_skip_loop }) => {
self.should_skip_squash_loop(vm, exec_scopes, should_skip_loop)
}
Hint::Core(CoreHint::Felt252DictEntryInit { dict_ptr, key }) => {
self.dict_entry_init(vm, exec_scopes, dict_ptr, key)
}
Hint::Core(CoreHint::Felt252DictEntryUpdate { dict_ptr, value }) => {
self.felt_252_dict_entry_update(vm, exec_scopes, dict_ptr, value)
}
Hint::Core(CoreHint::GetCurrentAccessDelta { index_delta_minus1 }) => {
self.get_current_access_delta(vm, exec_scopes, index_delta_minus1)
}
Hint::Core(CoreHint::InitSquashData {
dict_accesses,
n_accesses,
big_keys,
..
}) => self.init_squash_data(vm, exec_scopes, dict_accesses, n_accesses, big_keys),
Hint::Core(CoreHint::AllocConstantSize { size, dst }) => {
self.alloc_constant_size(vm, exec_scopes, size, dst)
}
Hint::Core(CoreHint::GetCurrentAccessIndex { range_check_ptr }) => {
self.get_current_access_index(vm, exec_scopes, range_check_ptr)
}
Hint::Core(CoreHint::ShouldContinueSquashLoop { should_continue }) => {
self.should_continue_squash_loop(vm, exec_scopes, should_continue)
}
Hint::Core(CoreHint::FieldSqrt { val, sqrt }) => self.field_sqrt(vm, val, sqrt),
hint => Err(HintError::UnknownHint(hint.to_string())),
}
}
Expand Down Expand Up @@ -586,6 +618,27 @@ impl Cairo1HintProcessor {
Ok(())
}

fn dict_entry_init(
&self,
vm: &mut VirtualMachine,
exec_scopes: &mut ExecutionScopes,
dict_ptr: &ResOperand,
key: &ResOperand,
) -> Result<(), HintError> {
let (dict_base, dict_offset) = extract_buffer(dict_ptr)?;
let dict_address = get_ptr(vm, dict_base, &dict_offset)?;
let key = res_operand_get_val(vm, key)?;
let dict_manager_exec_scope =
exec_scopes.get_mut_ref::<DictManagerExecScope>("dict_manager_exec_scope")?;

let prev_value = dict_manager_exec_scope
.get_from_tracker(dict_address, &key)
.unwrap_or_else(|| DictManagerExecScope::DICT_DEFAULT_VALUE.into());

vm.insert_value((dict_address + 1)?, prev_value)
.map_err(HintError::from)
}

fn debug_print(
&self,
vm: &mut VirtualMachine,
Expand Down Expand Up @@ -660,6 +713,217 @@ impl Cairo1HintProcessor {

Ok(())
}

fn felt_252_dict_entry_update(
&self,
vm: &mut VirtualMachine,
exec_scopes: &mut ExecutionScopes,
dict_ptr: &ResOperand,
value: &ResOperand,
) -> Result<(), HintError> {
let (dict_base, dict_offset) = extract_buffer(dict_ptr)?;
let dict_address = get_ptr(vm, dict_base, &dict_offset)?;
let key = get_double_deref_val(vm, dict_base, &(dict_offset + Felt252::from(-3)))?;
let value = res_operand_get_val(vm, value)?;
let dict_manager_exec_scope = exec_scopes
.get_mut_ref::<DictManagerExecScope>("dict_manager_exec_scope")
.map_err(|_| {
HintError::CustomHint(
"Trying to write to a dict while dict manager was not initialized.".to_string(),
)
})?;
dict_manager_exec_scope.insert_to_tracker(dict_address, key, value);

Ok(())
}

fn get_current_access_delta(
&self,
vm: &mut VirtualMachine,
exec_scopes: &mut ExecutionScopes,
index_delta_minus1: &CellRef,
) -> Result<(), HintError> {
let dict_squash_exec_scope: &mut DictSquashExecScope =
exec_scopes.get_mut_ref("dict_squash_exec_scope")?;
let prev_access_index = dict_squash_exec_scope
.pop_current_access_index()
.ok_or(HintError::CustomHint("no accessed index".to_string()))?;
let index_delta_minus_1_val = dict_squash_exec_scope
.current_access_index()
.ok_or(HintError::CustomHint("no index accessed".to_string()))?
.clone()
- prev_access_index
- 1_u32;

vm.insert_value(
cell_ref_to_relocatable(index_delta_minus1, vm)?,
index_delta_minus_1_val,
)?;

Ok(())
}

fn init_squash_data(
&self,
vm: &mut VirtualMachine,
exec_scopes: &mut ExecutionScopes,
dict_accesses: &ResOperand,
n_accesses: &ResOperand,
big_keys: &CellRef,
) -> Result<(), HintError> {
let dict_access_size = 3;
let rangecheck_bound = Felt252::from(u128::MAX) + 1u32;

exec_scopes.assign_or_update_variable(
"dict_squash_exec_scope",
Box::<DictSquashExecScope>::default(),
);
let dict_squash_exec_scope =
exec_scopes.get_mut_ref::<DictSquashExecScope>("dict_squash_exec_scope")?;
let (dict_accesses_base, dict_accesses_offset) = extract_buffer(dict_accesses)?;
let dict_accesses_address = get_ptr(vm, dict_accesses_base, &dict_accesses_offset)?;
let n_accesses =
res_operand_get_val(vm, n_accesses)?
.to_usize()
.ok_or(HintError::CustomHint(
"Number of accesses is too large or negative.".to_string(),
))?;

for i in 0..n_accesses {
let current_key = vm.get_integer((dict_accesses_address + i * dict_access_size)?)?;
dict_squash_exec_scope
.access_indices
.entry(current_key.into_owned())
.and_modify(|indices| indices.push(Felt252::from(i)))
.or_insert_with(|| vec![Felt252::from(i)]);
}
// Reverse the accesses in order to pop them in order later.
for (_, accesses) in dict_squash_exec_scope.access_indices.iter_mut() {
accesses.reverse();
}

dict_squash_exec_scope.keys = dict_squash_exec_scope
.access_indices
.keys()
.cloned()
.collect();
dict_squash_exec_scope.keys.sort_by(|a, b| b.cmp(a));
// big_keys indicates if the keys are greater than rangecheck_bound. If they are not
// a simple range check is used instead of assert_le_felt252.

let val = Felt252::from((dict_squash_exec_scope.keys[0] < rangecheck_bound) as u8);

vm.insert_value(cell_ref_to_relocatable(big_keys, vm)?, val)?;

vm.insert_value(
cell_ref_to_relocatable(big_keys, vm)?,
dict_squash_exec_scope
.current_key()
.ok_or(HintError::CustomHint("No current key".to_string()))?,
)?;

Ok(())
}

fn alloc_constant_size(
&self,
vm: &mut VirtualMachine,
exec_scopes: &mut ExecutionScopes,
size: &ResOperand,
dst: &CellRef,
) -> Result<(), HintError> {
let object_size = res_operand_get_val(vm, size)?
.to_usize()
.expect("Object size too large.");
let memory_exec_scope =
match exec_scopes.get_mut_ref::<MemoryExecScope>("memory_exec_scope") {
Ok(memory_exec_scope) => memory_exec_scope,
Err(_) => {
exec_scopes.assign_or_update_variable(
"memory_exec_scope",
Box::new(MemoryExecScope {
next_address: vm.add_memory_segment(),
}),
);
exec_scopes.get_mut_ref::<MemoryExecScope>("memory_exec_scope")?
}
};

vm.insert_value(
cell_ref_to_relocatable(dst, vm)?,
memory_exec_scope.next_address,
)?;

memory_exec_scope.next_address.offset += object_size;
Ok(())
}

fn get_current_access_index(
&self,
vm: &mut VirtualMachine,
exec_scopes: &mut ExecutionScopes,
range_check_ptr: &ResOperand,
) -> Result<(), HintError> {
let dict_squash_exec_scope: &mut DictSquashExecScope =
exec_scopes.get_mut_ref("dict_squash_exec_scope")?;
let (range_check_base, range_check_offset) = extract_buffer(range_check_ptr)?;
let range_check_ptr = get_ptr(vm, range_check_base, &range_check_offset)?;
let current_access_index =
dict_squash_exec_scope
.current_access_index()
.ok_or(HintError::CustomHint(
"No current accessed index".to_string(),
))?;
vm.insert_value(range_check_ptr, current_access_index)?;

Ok(())
}

fn should_continue_squash_loop(
&self,
vm: &mut VirtualMachine,
exec_scopes: &mut ExecutionScopes,
dst: &CellRef,
) -> Result<(), HintError> {
let dict_squash_exec_scope: &mut DictSquashExecScope =
exec_scopes.get_mut_ref("dict_squash_exec_scope")?;
let current_access_indices = dict_squash_exec_scope
.current_access_indices()
.ok_or(HintError::EmptyCurrentAccessIndices)?;

let should_continue = Felt252::from((current_access_indices.len() > 1) as u8);

vm.insert_value(cell_ref_to_relocatable(dst, vm)?, should_continue)
.map_err(HintError::from)
}

fn field_sqrt(
&self,
vm: &mut VirtualMachine,
val: &ResOperand,
sqrt: &CellRef,
) -> Result<(), HintError> {
let value = Fq::from(res_operand_get_val(vm, val)?.to_biguint());

let three_fq = Fq::from(Felt252::new(3).to_biguint());
let res = if value.legendre().is_qr() {
value
} else {
value * three_fq
};

if let Some(root) = res.sqrt() {
let root0: BigUint = root.into_bigint().into();
let root1: BigUint = (-root).into_bigint().into();
let root = Felt252::from(std::cmp::min(root0, root1));
vm.insert_value(cell_ref_to_relocatable(sqrt, vm)?, root)
.map_err(HintError::from)
} else {
Err(HintError::CustomHint(
"Field element is not a square".to_string(),
))
}
}
}

impl HintProcessor for Cairo1HintProcessor {
Expand Down

0 comments on commit 22cb518

Please sign in to comment.