diff --git a/crates/evm/src/errors.cairo b/crates/evm/src/errors.cairo index a6cecf9c6..21a2c8419 100644 --- a/crates/evm/src/errors.cairo +++ b/crates/evm/src/errors.cairo @@ -19,6 +19,7 @@ const WRITE_IN_STATIC_CONTEXT: felt252 = 'KKT: WriteInStaticContext'; // STARKNET_SYSCALLS const READ_SYSCALL_FAILED: felt252 = 'KKT: read syscall failed'; +const BLOCK_HASH_SYSCALL_FAILED: felt252 = 'KKT: block_hash syscall failed'; const WRITE_SYSCALL_FAILED: felt252 = 'KKT: write syscall failed'; #[derive(Drop, Copy, PartialEq)] diff --git a/crates/evm/src/instructions/block_information.cairo b/crates/evm/src/instructions/block_information.cairo index 918183c2b..8360f72b1 100644 --- a/crates/evm/src/instructions/block_information.cairo +++ b/crates/evm/src/instructions/block_information.cairo @@ -1,12 +1,13 @@ use evm::balance::balance; //! Block Information. -use evm::errors::EVMError; +use evm::errors::{EVMError, BLOCK_HASH_SYSCALL_FAILED}; use evm::machine::{Machine, MachineCurrentContextTrait}; use evm::stack::StackTrait; // Corelib imports -use starknet::info::{get_block_number, get_block_timestamp}; +use starknet::info::{get_block_number, get_block_timestamp, get_block_info}; +use starknet::{get_block_hash_syscall}; use utils::constants::CHAIN_ID; #[generate_trait] @@ -15,7 +16,27 @@ impl BlockInformation of BlockInformationTrait { /// Get the hash of one of the 256 most recent complete blocks. /// # Specification: https://www.evm.codes/#40?fork=shanghai fn exec_blockhash(ref self: Machine) -> Result<(), EVMError> { - Result::Err(EVMError::NotImplemented) + let block_number = self.stack.pop_u64()?; + let current_block = get_block_number(); + + // If input block number is lower than current_block - 256, return 0 + // If input block number is higher than current_block - 10, return 0 + // Note: in the specs, input block number can be equal - at most - to the current block number minus one. + // In Starknet, the `get_block_hash_syscall` is capped at current block minus ten. + // TODO: monitor the changes in the `get_block_hash_syscall` syscall. + // source: https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/system-calls-cairo1/#get_block_hash + if block_number + 10 > current_block || block_number + 256 < current_block { + return self.stack.push(0); + } + + let maybe_block_hash = get_block_hash_syscall(block_number); + match maybe_block_hash { + Result::Ok(block_hash) => self.stack.push(block_hash.into()), + // This syscall should not error out, as we made sure block_number =< current_block - 10 + // In case of failed syscall, we can either return 0, or revert. + // Since this situation would be highly breaking, we choose to revert. + Result::Err(_) => Result::Err(EVMError::SyscallFailed(BLOCK_HASH_SYSCALL_FAILED)), + } } /// 0x41 - COINBASE diff --git a/crates/evm/src/stack.cairo b/crates/evm/src/stack.cairo index 89a936896..6e6cf2699 100644 --- a/crates/evm/src/stack.cairo +++ b/crates/evm/src/stack.cairo @@ -35,6 +35,7 @@ trait StackTrait { fn push(ref self: Stack, item: u256) -> Result<(), EVMError>; fn pop(ref self: Stack) -> Result; fn pop_usize(ref self: Stack) -> Result; + fn pop_u64(ref self: Stack) -> Result; fn pop_i256(ref self: Stack) -> Result; fn pop_eth_address(ref self: Stack) -> Result; fn pop_n(ref self: Stack, n: usize) -> Result, EVMError>; @@ -113,11 +114,25 @@ impl StackImpl of StackTrait { #[inline(always)] fn pop_usize(ref self: Stack) -> Result { let item: u256 = self.pop()?; - // item.try_into().ok_or(EVMError::TypeConversionError(TYPE_CONVERSION_ERROR)) let item: usize = item.try_into_result()?; Result::Ok(item) } + /// Calls `Stack::pop` and tries to convert it to u64 + /// + /// # Errors + /// + /// Returns `EVMError::StackError` with appropriate message + /// In case: + /// - Stack is empty + /// - Type conversion failed + #[inline(always)] + fn pop_u64(ref self: Stack) -> Result { + let item: u256 = self.pop()?; + let item: u64 = item.try_into_result()?; + Result::Ok(item) + } + /// Calls `Stack::pop` and convert it to i256 /// /// # Errors diff --git a/crates/evm/src/tests/test_instructions/test_block_information.cairo b/crates/evm/src/tests/test_instructions/test_block_information.cairo index 23f053058..564ef3bd9 100644 --- a/crates/evm/src/tests/test_instructions/test_block_information.cairo +++ b/crates/evm/src/tests/test_instructions/test_block_information.cairo @@ -9,6 +9,58 @@ use openzeppelin::token::erc20::interface::IERC20CamelDispatcherTrait; use starknet::testing::{set_block_timestamp, set_block_number, set_contract_address}; use utils::constants::CHAIN_ID; +/// 0x40 - BLOCKHASH +#[test] +#[available_gas(20000000)] +fn test_exec_blockhash_below_bounds() { + // Given + let mut machine = setup_machine(); + + set_block_number(500); + + // When + machine.stack.push(243).unwrap(); + machine.exec_blockhash(); + + // Then + assert(machine.stack.peek().unwrap() == 0, 'stack top should be 0'); +} + +#[test] +#[available_gas(20000000)] +fn test_exec_blockhash_above_bounds() { + // Given + let mut machine = setup_machine(); + + set_block_number(500); + + // When + machine.stack.push(491).unwrap(); + machine.exec_blockhash(); + + // Then + assert(machine.stack.peek().unwrap() == 0, 'stack top should be 0'); +} + +// TODO: implement exec_blockhash testing for block number within bounds +// https://github.com/starkware-libs/cairo/blob/77a7e7bc36aa1c317bb8dd5f6f7a7e6eef0ab4f3/crates/cairo-lang-starknet/cairo_level_tests/interoperability.cairo#L173 +#[ignore] +#[test] +#[available_gas(20000000)] +fn test_exec_blockhash_within_bounds() { + // Given + let mut machine = setup_machine(); + + set_block_number(500); + + // When + machine.stack.push(244).unwrap(); + machine.exec_blockhash(); + // Then + assert(machine.stack.peek().unwrap() == 0xF, 'stack top should be 0xF'); +} + + #[test] #[available_gas(20000000)] fn test_block_timestamp_set_to_1692873993() { diff --git a/crates/utils/src/traits.cairo b/crates/utils/src/traits.cairo index 0691a7877..f5b2e24b8 100644 --- a/crates/utils/src/traits.cairo +++ b/crates/utils/src/traits.cairo @@ -85,11 +85,11 @@ trait TryIntoResult { fn try_into_result(self: T) -> Result; } -impl U256TryIntoResultU32 of TryIntoResult { - /// Converts a u256 into a Result - /// If the u256 is larger than MAX_U32, it returns an error. +impl U256TryIntoResult> of TryIntoResult { + /// Converts a u256 into a Result + /// If the u256 cannot be converted into U, it returns an error. /// Otherwise, it returns the casted value. - fn try_into_result(self: u256) -> Result { + fn try_into_result(self: u256) -> Result { match self.try_into() { Option::Some(value) => Result::Ok(value), Option::None => Result::Err(EVMError::TypeConversionError(TYPE_CONVERSION_ERROR))