Skip to content

Commit

Permalink
feat: add native syscall handler
Browse files Browse the repository at this point in the history
  • Loading branch information
rodrigo-pino committed Oct 3, 2024
1 parent 5e181ad commit b15faf6
Show file tree
Hide file tree
Showing 4 changed files with 409 additions and 0 deletions.
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;
323 changes: 323 additions & 0 deletions crates/blockifier/src/execution/native/syscall_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
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(),
}
}

pub 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.0.clone();

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

self.update_remaining_gas(remaining_gas, &call_info);

let retdata = call_info.execution.retdata.clone();

self.inner_calls.push(call_info);

Ok(retdata)
}

pub fn update_remaining_gas(&mut self, remaining_gas: &mut u128, call_info: &CallInfo) {
// Create a new variable with converted type.
let mut remaining_gas_u64 = u64::try_from(*remaining_gas).unwrap();

// 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);
}

// 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.
pub 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.");
}
}
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

0 comments on commit b15faf6

Please sign in to comment.