diff --git a/Makefile b/Makefile index 7c734a499..15d4ae553 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ UNAME := $(shell uname) CAIRO_2_VERSION = 2.9.0-dev.0 -SCARB_VERSION = 2.9.0-dev.0 +SCARB_VERSION = 2.8.4 # Usage is the default target for newcomers running `make`. .PHONY: usage diff --git a/examples/erc20.rs b/examples/erc20.rs index 76917c32e..b466251fc 100644 --- a/examples/erc20.rs +++ b/examples/erc20.rs @@ -274,6 +274,14 @@ impl StarknetSyscallHandler for SyscallHandler { ) -> SyscallResult<()> { unimplemented!() } + + fn get_class_hash_at( + &mut self, + _contract_address: Felt, + _remaining_gas: &mut u128, + ) -> SyscallResult { + todo!() + } } fn main() { diff --git a/examples/starknet.rs b/examples/starknet.rs index 28f987041..a2d8ceb16 100644 --- a/examples/starknet.rs +++ b/examples/starknet.rs @@ -405,6 +405,14 @@ impl StarknetSyscallHandler for SyscallHandler { ) -> SyscallResult<()> { unimplemented!() } + + fn get_class_hash_at( + &mut self, + _contract_address: Felt, + _remaining_gas: &mut u128, + ) -> SyscallResult { + todo!() + } } fn main() { diff --git a/src/libfuncs/starknet.rs b/src/libfuncs/starknet.rs index 2bb72bb7a..0759f0d0b 100644 --- a/src/libfuncs/starknet.rs +++ b/src/libfuncs/starknet.rs @@ -144,7 +144,9 @@ pub fn build<'ctx, 'this>( StarkNetConcreteLibfunc::Sha256StateHandleDigest(info) => build_sha256_state_handle_digest( context, registry, entry, location, helper, metadata, info, ), - StarkNetConcreteLibfunc::GetClassHashAt(_) => todo!("2.9.0"), + StarkNetConcreteLibfunc::GetClassHashAt(info) => { + build_get_class_hash_at(context, registry, entry, location, helper, metadata, info) + } #[cfg(feature = "with-cheatcode")] StarkNetConcreteLibfunc::Testing(TestingConcreteLibfunc::Cheatcode(info)) => { self::testing::build(context, registry, entry, location, helper, metadata, info) @@ -3137,6 +3139,193 @@ pub fn build_sha256_process_block_syscall<'ctx, 'this>( Ok(()) } +pub fn build_get_class_hash_at<'ctx, 'this>( + context: &'ctx Context, + registry: &ProgramRegistry, + entry: &'this Block<'ctx>, + location: Location<'ctx>, + helper: &LibfuncHelper<'ctx, 'this>, + metadata: &mut MetadataStorage, + info: &SignatureOnlyConcreteLibfunc, +) -> Result<()> { + // Extract self pointer. + let ptr = entry.load( + context, + location, + entry.argument(1)?.into(), + llvm::r#type::pointer(context, 0), + )?; + + // Allocate space for the return value. + let (result_layout, (result_tag_ty, result_tag_layout), variant_tys) = + crate::types::r#enum::get_type_for_variants( + context, + helper, + registry, + metadata, + &[ + info.branch_signatures()[0].vars[2].ty.clone(), + info.branch_signatures()[1].vars[2].ty.clone(), + ], + )?; + + let result_ptr = helper.init_block().alloca1( + context, + location, + llvm::r#type::r#struct( + context, + &[ + result_tag_ty, + llvm::r#type::array( + IntegerType::new(context, 8).into(), + (result_layout.size() - 1).try_into()?, + ), + ], + false, + ), + result_layout.align(), + )?; + + // Allocate space and write the current gas. + let gas_builtin_ptr = helper.init_block().alloca1( + context, + location, + IntegerType::new(context, 128).into(), + get_integer_layout(128).align(), + )?; + entry.store( + context, + location, + gas_builtin_ptr, + entry.argument(0)?.into(), + )?; + + // Allocate `contract_address` argument and write the value. + let contract_address_ptr = helper.init_block().alloca_int(context, location, 252)?; + entry.store( + context, + location, + contract_address_ptr, + entry.argument(2)?.into(), + )?; + + // Extract function pointer. + let fn_ptr = entry.append_op_result(llvm::get_element_ptr( + context, + entry.argument(1)?.into(), + DenseI32ArrayAttribute::new( + context, + &[StarknetSyscallHandlerCallbacks::<()>::GET_CLASS_HASH_AT.try_into()?], + ), + llvm::r#type::pointer(context, 0), + llvm::r#type::pointer(context, 0), + location, + ))?; + let fn_ptr = entry.load(context, location, fn_ptr, llvm::r#type::pointer(context, 0))?; + + entry.append_operation( + OperationBuilder::new("llvm.call", location) + .add_operands(&[ + fn_ptr, + result_ptr, + ptr, + gas_builtin_ptr, + contract_address_ptr, + ]) + .build()?, + ); + + let result = entry.load( + context, + location, + result_ptr, + llvm::r#type::r#struct( + context, + &[ + result_tag_ty, + llvm::r#type::array( + IntegerType::new(context, 8).into(), + (result_layout.size() - 1).try_into()?, + ), + ], + false, + ), + )?; + let result_tag = entry.extract_value( + context, + location, + result, + IntegerType::new(context, 1).into(), + 0, + )?; + + let payload_ok = { + let ptr = entry.append_op_result( + OperationBuilder::new("llvm.getelementptr", location) + .add_attributes(&[ + ( + Identifier::new(context, "rawConstantIndices"), + DenseI32ArrayAttribute::new( + context, + &[result_tag_layout.extend(variant_tys[0].1)?.1.try_into()?], + ) + .into(), + ), + ( + Identifier::new(context, "elem_type"), + TypeAttribute::new(IntegerType::new(context, 8).into()).into(), + ), + ]) + .add_operands(&[result_ptr]) + .add_results(&[llvm::r#type::pointer(context, 0)]) + .build()?, + )?; + entry.load(context, location, ptr, variant_tys[0].0)? + }; + let payload_err = { + let ptr = entry.append_op_result( + OperationBuilder::new("llvm.getelementptr", location) + .add_attributes(&[ + ( + Identifier::new(context, "rawConstantIndices"), + DenseI32ArrayAttribute::new( + context, + &[result_tag_layout.extend(variant_tys[1].1)?.1.try_into()?], + ) + .into(), + ), + ( + Identifier::new(context, "elem_type"), + TypeAttribute::new(IntegerType::new(context, 8).into()).into(), + ), + ]) + .add_operands(&[result_ptr]) + .add_results(&[llvm::r#type::pointer(context, 0)]) + .build()?, + )?; + entry.load(context, location, ptr, variant_tys[1].0)? + }; + + let remaining_gas = entry.load( + context, + location, + gas_builtin_ptr, + IntegerType::new(context, 128).into(), + )?; + + entry.append_operation(helper.cond_br( + context, + result_tag, + [1, 0], + [ + &[remaining_gas, entry.argument(1)?.into(), payload_err], + &[remaining_gas, entry.argument(1)?.into(), payload_ok], + ], + location, + )); + Ok(()) +} + #[cfg(test)] mod test { use crate::utils::test::{jit_enum, jit_struct, load_cairo, run_program_assert_output}; diff --git a/src/starknet.rs b/src/starknet.rs index 5371e0364..994b386d9 100644 --- a/src/starknet.rs +++ b/src/starknet.rs @@ -325,6 +325,12 @@ pub trait StarknetSyscallHandler { remaining_gas: &mut u128, ) -> SyscallResult<()>; + fn get_class_hash_at( + &mut self, + contract_address: Felt, + remaining_gas: &mut u128, + ) -> SyscallResult; + #[cfg(feature = "with-cheatcode")] fn cheatcode(&mut self, _selector: Felt, _input: &[Felt]) -> Vec { unimplemented!(); @@ -525,6 +531,14 @@ impl StarknetSyscallHandler for DummySyscallHandler { ) -> SyscallResult<()> { unimplemented!() } + + fn get_class_hash_at( + &mut self, + _contract_address: Felt, + _remaining_gas: &mut u128, + ) -> SyscallResult { + unimplemented!() + } } // TODO: Move to the correct place or remove if unused. @@ -801,6 +815,12 @@ pub(crate) mod handler { state: *mut [u32; 8], block: &[u32; 16], ), + get_class_hash_at: extern "C" fn( + result_ptr: &mut SyscallResultAbi, + ptr: &mut T, + gas: &mut u128, + contract_address: &Felt252Abi, + ), // testing syscalls #[cfg(feature = "with-cheatcode")] pub cheatcode: extern "C" fn( @@ -841,7 +861,10 @@ pub(crate) mod handler { pub const SECP256R1_GET_POINT_FROM_X: usize = field_offset!(Self, secp256r1_get_point_from_x) >> 3; pub const SECP256R1_GET_XY: usize = field_offset!(Self, secp256r1_get_xy) >> 3; + pub const SHA256_PROCESS_BLOCK: usize = field_offset!(Self, sha256_process_block) >> 3; + + pub const GET_CLASS_HASH_AT: usize = field_offset!(Self, get_class_hash_at) >> 3; } #[allow(unused_variables)] @@ -875,6 +898,7 @@ pub(crate) mod handler { secp256r1_get_point_from_x: Self::wrap_secp256r1_get_point_from_x, secp256r1_get_xy: Self::wrap_secp256r1_get_xy, sha256_process_block: Self::wrap_sha256_process_block, + get_class_hash_at: Self::wrap_get_class_hash_at, #[cfg(feature = "with-cheatcode")] cheatcode: Self::wrap_cheatcode, } @@ -1120,8 +1144,6 @@ pub(crate) mod handler { }; } - // TODO: change all from_bytes_be to from_bytes_ne when added and undo byte swapping. - extern "C" fn wrap_deploy( result_ptr: &mut SyscallResultAbi<(Felt252Abi, ArrayAbi)>, ptr: &mut T, @@ -1636,6 +1658,25 @@ pub(crate) mod handler { Err(e) => Self::wrap_error(&e), }; } + + extern "C" fn wrap_get_class_hash_at( + result_ptr: &mut SyscallResultAbi, + ptr: &mut T, + gas: &mut u128, + contract_address: &Felt252Abi, + ) { + let result = ptr.get_class_hash_at(contract_address.into(), gas); + + *result_ptr = match result { + Ok(x) => SyscallResultAbi { + ok: ManuallyDrop::new(SyscallResultAbiOk { + tag: 0u8, + payload: ManuallyDrop::new(Felt252Abi(x.to_bytes_le())), + }), + }, + Err(e) => Self::wrap_error(&e), + }; + } } } diff --git a/src/starknet_stub.rs b/src/starknet_stub.rs index bf25d22c1..77e095fe5 100644 --- a/src/starknet_stub.rs +++ b/src/starknet_stub.rs @@ -647,6 +647,14 @@ impl StarknetSyscallHandler for &mut StubSyscallHandler { sha2::compress256(state, &[data_as_bytes]); Ok(()) } + + fn get_class_hash_at( + &mut self, + contract_address: Felt, + _remaining_gas: &mut u128, + ) -> SyscallResult { + Ok(contract_address) + } } #[cfg(test)] diff --git a/tests/tests/starknet/programs/syscalls.cairo b/tests/tests/starknet/programs/syscalls.cairo index c57362bc4..72d6d5266 100644 --- a/tests/tests/starknet/programs/syscalls.cairo +++ b/tests/tests/starknet/programs/syscalls.cairo @@ -4,10 +4,11 @@ use core::starknet::{ keccak_syscall, library_call_syscall, replace_class_syscall, send_message_to_l1_syscall, storage_address_try_from_felt252, storage_read_syscall, storage_write_syscall, SyscallResult, - testing::cheatcode, + testing::cheatcode, class_hash::ClassHash }; use core::starknet::syscalls::get_execution_info_syscall; use core::starknet::syscalls::get_execution_info_v2_syscall; +use core::starknet::syscalls::get_class_hash_at_syscall; use core::sha256::{sha256_state_handle_init, sha256_state_handle_digest, SHA256_INITIAL_STATE}; use core::box::BoxTrait; use core::starknet::SyscallResultTrait; @@ -16,6 +17,10 @@ fn get_block_hash() -> SyscallResult { get_block_hash_syscall(0) } +fn get_class_hash_at() -> SyscallResult { + get_class_hash_at_syscall(contract_address_const::<2>()) +} + fn get_execution_info() -> SyscallResult> { get_execution_info_syscall() } diff --git a/tests/tests/starknet/secp256.rs b/tests/tests/starknet/secp256.rs index 28a8f9ce6..f98bb5a38 100644 --- a/tests/tests/starknet/secp256.rs +++ b/tests/tests/starknet/secp256.rs @@ -256,6 +256,14 @@ impl StarknetSyscallHandler for &mut SyscallHandler { ) -> SyscallResult<()> { unimplemented!() } + + fn get_class_hash_at( + &mut self, + _contract_address: Felt, + _remaining_gas: &mut u128, + ) -> SyscallResult { + unimplemented!() + } } lazy_static! { diff --git a/tests/tests/starknet/syscalls.rs b/tests/tests/starknet/syscalls.rs index fc52ee5f0..6e3bbc5c9 100644 --- a/tests/tests/starknet/syscalls.rs +++ b/tests/tests/starknet/syscalls.rs @@ -502,6 +502,14 @@ impl StarknetSyscallHandler for SyscallHandler { ) -> SyscallResult<()> { Ok(()) } + + fn get_class_hash_at( + &mut self, + contract_address: Felt, + _remaining_gas: &mut u128, + ) -> SyscallResult { + Ok(contract_address) + } } lazy_static! { @@ -1200,3 +1208,23 @@ fn sha256_process() { }, ); } + +#[test] +fn get_class_hash_at() { + let result = run_native_program( + &SYSCALLS_PROGRAM, + "get_class_hash_at", + &[], + Some(u128::MAX), + Some(SyscallHandler::new()), + ); + + assert_eq_sorted!( + result.return_value, + Value::Enum { + tag: 0, + value: Box::new(Value::Felt252(2.into())), + debug_name: None, + }, + ); +}