Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(blockifier): add NativeSyscallHandler #1240

Merged
merged 7 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/blockifier/src/execution/native.rs
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
pub mod syscall_handler;
pub mod utils;

#[cfg(test)]
pub mod utils_test;
328 changes: 328 additions & 0 deletions crates/blockifier/src/execution/native/syscall_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
use std::collections::HashSet;
use std::hash::RandomState;

use cairo_native::starknet::{
ExecutionInfo,
ExecutionInfoV2,
Secp256k1Point,
Secp256r1Point,
StarknetSyscallHandler,
SyscallResult,
U256,
};
use cairo_vm::vm::runners::cairo_runner::ExecutionResources;
use starknet_api::core::{ContractAddress, EntryPointSelector};
use starknet_api::state::StorageKey;
use starknet_types_core::felt::Felt;

use crate::execution::call_info::{CallInfo, OrderedEvent, OrderedL2ToL1Message, Retdata};
use crate::execution::entry_point::{CallEntryPoint, EntryPointExecutionContext};
use crate::execution::execution_utils::update_remaining_gas;
use crate::execution::native::utils::encode_str_as_felts;
use crate::execution::syscalls::hint_processor::OUT_OF_GAS_ERROR;
use crate::state::state_api::State;

pub struct NativeSyscallHandler<'state> {
// Input for execution.
pub state: &'state mut dyn State,
pub resources: &'state mut ExecutionResources,
pub context: &'state mut EntryPointExecutionContext,

// Call information.
pub caller_address: ContractAddress,
pub contract_address: ContractAddress,
pub entry_point_selector: Felt,

// Execution results.
pub events: Vec<OrderedEvent>,
pub l2_to_l1_messages: Vec<OrderedL2ToL1Message>,
pub inner_calls: Vec<CallInfo>,

// Additional information gathered during execution.
pub read_values: Vec<Felt>,
pub accessed_keys: HashSet<StorageKey, RandomState>,
}

impl<'state> NativeSyscallHandler<'state> {
pub fn new(
state: &'state mut dyn State,
caller_address: ContractAddress,
contract_address: ContractAddress,
entry_point_selector: EntryPointSelector,
resources: &'state mut ExecutionResources,
context: &'state mut EntryPointExecutionContext,
) -> NativeSyscallHandler<'state> {
NativeSyscallHandler {
state,
caller_address,
contract_address,
entry_point_selector: entry_point_selector.0,
resources,
context,
events: Vec::new(),
l2_to_l1_messages: Vec::new(),
inner_calls: Vec::new(),
read_values: Vec::new(),
accessed_keys: HashSet::new(),
}
}

#[allow(dead_code)]
fn execute_inner_call(
&mut self,
entry_point: CallEntryPoint,
remaining_gas: &mut u128,
) -> SyscallResult<Retdata> {
let call_info = entry_point
.execute(self.state, self.resources, self.context)
.map_err(|e| encode_str_as_felts(&e.to_string()))?;
let retdata = call_info.execution.retdata.clone();

if call_info.execution.failed {
// In VM it's wrapped into `SyscallExecutionError::SyscallError`.
return Err(retdata.0.clone());
}

native_update_remaining_gas(remaining_gas, &call_info);

self.inner_calls.push(call_info);

Ok(retdata)
}

// Handles gas related logic when executing a syscall. Required because Native calls the
// syscalls directly unlike the VM where the `execute_syscall` method perform this operation
// first.
#[allow(dead_code)]
fn substract_syscall_gas_cost(
&mut self,
remaining_gas: &mut u128,
syscall_gas_cost: u64,
) -> SyscallResult<()> {
// Refund `SYSCALL_BASE_GAS_COST` as it was pre-charged.
let required_gas =
u128::from(syscall_gas_cost - self.context.gas_costs().syscall_base_gas_cost);

if *remaining_gas < required_gas {
// Out of gas failure.
return Err(vec![
Felt::from_hex(OUT_OF_GAS_ERROR)
.expect("Failed to parse OUT_OF_GAS_ERROR hex string"),
]);
}

*remaining_gas -= required_gas;

Ok(())
}
}

impl<'state> StarknetSyscallHandler for &mut NativeSyscallHandler<'state> {
fn get_block_hash(
&mut self,
_block_number: u64,
_remaining_gas: &mut u128,
) -> SyscallResult<Felt> {
todo!("Implement get_block_hash syscall.");
}

fn get_execution_info(&mut self, _remaining_gas: &mut u128) -> SyscallResult<ExecutionInfo> {
todo!("Implement get_execution_info syscall.");
}

fn get_execution_info_v2(
&mut self,
_remaining_gas: &mut u128,
) -> SyscallResult<ExecutionInfoV2> {
todo!("Implement get_execution_info_v2 syscall.");
}

fn deploy(
&mut self,
_class_hash: Felt,
_contract_address_salt: Felt,
_calldata: &[Felt],
_deploy_from_zero: bool,
_remaining_gas: &mut u128,
) -> SyscallResult<(Felt, Vec<Felt>)> {
todo!("Implement deploy syscall.");
}

fn replace_class(&mut self, _class_hash: Felt, _remaining_gas: &mut u128) -> SyscallResult<()> {
todo!("Implement replace_class syscall.");
}

fn library_call(
&mut self,
_class_hash: Felt,
_function_selector: Felt,
_calldata: &[Felt],
_remaining_gas: &mut u128,
) -> SyscallResult<Vec<Felt>> {
todo!("Implement library_call syscall.");
}

fn call_contract(
&mut self,
_address: Felt,
_entry_point_selector: Felt,
_calldata: &[Felt],
_remaining_gas: &mut u128,
) -> SyscallResult<Vec<Felt>> {
todo!("Implement call_contract syscall.");
}

fn storage_read(
&mut self,
_address_domain: u32,
_address: Felt,
_remaining_gas: &mut u128,
) -> SyscallResult<Felt> {
todo!("Implement storage_read syscall.");
}

fn storage_write(
&mut self,
_address_domain: u32,
_address: Felt,
_value: Felt,
_remaining_gas: &mut u128,
) -> SyscallResult<()> {
todo!("Implement storage_write syscall.");
}

fn emit_event(
&mut self,
_keys: &[Felt],
_data: &[Felt],
_remaining_gas: &mut u128,
) -> SyscallResult<()> {
todo!("Implement emit_event syscall.");
}

fn send_message_to_l1(
&mut self,
_to_address: Felt,
_payload: &[Felt],
_remaining_gas: &mut u128,
) -> SyscallResult<()> {
todo!("Implement send_message_to_l1 syscall.");
}

fn keccak(&mut self, _input: &[u64], _remaining_gas: &mut u128) -> SyscallResult<U256> {
todo!("Implement keccak syscall.");
}

fn secp256k1_new(
&mut self,
_x: U256,
_y: U256,
_remaining_gas: &mut u128,
) -> SyscallResult<Option<Secp256k1Point>> {
todo!("Implement secp256k1_new syscall.");
}

fn secp256k1_add(
&mut self,
_p0: Secp256k1Point,
_p1: Secp256k1Point,
_remaining_gas: &mut u128,
) -> SyscallResult<Secp256k1Point> {
todo!("Implement secp256k1_add syscall.");
}

fn secp256k1_mul(
&mut self,
_p: Secp256k1Point,
_m: U256,
_remaining_gas: &mut u128,
) -> SyscallResult<Secp256k1Point> {
todo!("Implement secp256k1_mul syscall.");
}

fn secp256k1_get_point_from_x(
&mut self,
_x: U256,
_y_parity: bool,
_remaining_gas: &mut u128,
) -> SyscallResult<Option<Secp256k1Point>> {
todo!("Implement secp256k1_get_point_from_x syscall.");
}

fn secp256k1_get_xy(
&mut self,
_p: Secp256k1Point,
_remaining_gas: &mut u128,
) -> SyscallResult<(U256, U256)> {
todo!("Implement secp256k1_get_xy syscall.");
}

fn secp256r1_new(
&mut self,
_x: U256,
_y: U256,
_remaining_gas: &mut u128,
) -> SyscallResult<Option<Secp256r1Point>> {
todo!("Implement secp256r1_new syscall.");
}

fn secp256r1_add(
&mut self,
_p0: Secp256r1Point,
_p1: Secp256r1Point,
_remaining_gas: &mut u128,
) -> SyscallResult<Secp256r1Point> {
todo!("Implement secp256r1_add syscall.");
}

fn secp256r1_mul(
&mut self,
_p: Secp256r1Point,
_m: U256,
_remaining_gas: &mut u128,
) -> SyscallResult<Secp256r1Point> {
todo!("Implement secp256r1_mul syscall.");
}

fn secp256r1_get_point_from_x(
&mut self,
_x: U256,
_y_parity: bool,
_remaining_gas: &mut u128,
) -> SyscallResult<Option<Secp256r1Point>> {
todo!("Implement secp256r1_get_point_from_x syscall.");
}

fn secp256r1_get_xy(
&mut self,
_p: Secp256r1Point,
_remaining_gas: &mut u128,
) -> SyscallResult<(U256, U256)> {
todo!("Implement secp256r1_get_xy syscall.");
}

fn sha256_process_block(
&mut self,
_prev_state: &[u32; 8],
_current_block: &[u32; 16],
_remaining_gas: &mut u128,
) -> SyscallResult<[u32; 8]> {
todo!("Implement sha256_process_block syscall.");
}
}

/// Wrapper function around [update_remaining_gas] which takes a u128 as an input, converts it to
/// u64 and uses [update_remaining_gas] to withdraw the right amount. Finally, updates the value
/// to which `remaining_gas` points to.
#[allow(dead_code)]
fn native_update_remaining_gas(remaining_gas: &mut u128, call_info: &CallInfo) {
// Create a new variable with converted type.
let mut remaining_gas_u64 =
u64::try_from(*remaining_gas).expect("Failed to convert gas to u64.");

// Pass the reference to the function.
update_remaining_gas(&mut remaining_gas_u64, call_info);

// Change the remaining gas value.
*remaining_gas = u128::from(remaining_gas_u64);
}
39 changes: 39 additions & 0 deletions crates/blockifier/src/execution/native/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use cairo_lang_starknet_classes::contract_class::ContractEntryPoint;
use itertools::Itertools;
use starknet_api::core::EntryPointSelector;
use starknet_types_core::felt::Felt;

Expand All @@ -7,3 +8,41 @@ pub fn contract_entrypoint_to_entrypoint_selector(
) -> EntryPointSelector {
EntryPointSelector(Felt::from(&entrypoint.selector))
}

pub fn encode_str_as_felts(msg: &str) -> Vec<Felt> {
const CHUNK_SIZE: usize = 32;

let data = msg.as_bytes().chunks(CHUNK_SIZE - 1);
let mut encoding = vec![Felt::default(); data.len()];
for (i, data_chunk) in data.enumerate() {
let mut chunk = [0_u8; CHUNK_SIZE];
chunk[1..data_chunk.len() + 1].copy_from_slice(data_chunk);
encoding[i] = Felt::from_bytes_be(&chunk);
}
encoding
}

// Todo(rodrigo): This is an opinionated way of interpretting error messages. It's ok for now but I
// think it can be improved; (for example) trying to make the output similar to a Cairo VM panic
pub fn decode_felts_as_str(encoding: &[Felt]) -> String {
let bytes_err: Vec<_> =
encoding.iter().flat_map(|felt| felt.to_bytes_be()[1..32].to_vec()).collect();

match String::from_utf8(bytes_err) {
// If the string is utf8 make sure it is not prefixed by no null chars. Null chars in
// between can still happen
Ok(s) => s.trim_matches('\0').to_owned(),
// If the string is non-utf8 overall, try to decode them as utf8 chunks of it and keep the
// original bytes for the non-utf8 chunks
Err(_) => {
let err_msgs = encoding
.iter()
.map(|felt| match String::from_utf8(felt.to_bytes_be()[1..32].to_vec()) {
Ok(s) => format!("{} ({})", s.trim_matches('\0'), felt),
Err(_) => felt.to_string(),
})
.join(", ");
format!("[{}]", err_msgs)
}
}
}
Loading
Loading