diff --git a/Cargo.lock b/Cargo.lock index fb83a7343..8b0b465e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1573,6 +1573,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca" +[[package]] +name = "contract_execution" +version = "0.4.0" +dependencies = [ + "cairo-vm", + "serde_json", + "starknet_in_rust", +] + [[package]] name = "convert_case" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 0a05305b0..90a1e6382 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ cairo_1_tests = [] metrics = [] [workspace] -members = ["cli", "fuzzer", "rpc_state_reader"] +members = ["cli", "fuzzer", "rpc_state_reader", "examples/contract_execution"] [workspace.dependencies] cairo-lang-casm = "2.2.0" @@ -35,7 +35,7 @@ cairo-lang-sierra = { workspace = true } cairo-lang-starknet = { workspace = true } cairo-lang-utils = { workspace = true } cairo-native = { git = "https://github.com/lambdaclass/cairo_native", rev = "4012a10b97530e208b76d42169aa9608a6a9d8fd", optional = true } -cairo-vm = { workspace = true, features = ["cairo-1-hints"] } +cairo-vm = { workspace = true } flate2 = "1.0.25" getset = "0.1.2" hex = "0.4.3" diff --git a/examples/contract_execution/Cargo.toml b/examples/contract_execution/Cargo.toml new file mode 100644 index 000000000..b2b76bbe0 --- /dev/null +++ b/examples/contract_execution/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "contract_execution" +version = "0.4.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cairo-vm = { workspace = true } +starknet_in_rust = { path = "../../", version = "0.4.0" } +serde_json = { version = "1.0", features = [ + "arbitrary_precision", + "raw_value", +] } diff --git a/examples/contract_execution/README.md b/examples/contract_execution/README.md index 88c8faf54..20aa5dbd9 100644 --- a/examples/contract_execution/README.md +++ b/examples/contract_execution/README.md @@ -10,17 +10,12 @@ As declare and deploy transactions are currently WIP, we encapsulate all the fun - Add your contract to this directory. - - Remember that in order to call functions you must use the *external* decorator. - - - You also must add ```%lang starknet``` at the beggining of the contract. - - - Compile the contract: - ```source starknet-venv/bin/activate``` - - ```starknet-compile your_contract.cairo --output your_contract.json``` + - ```cairo2/bin/starknet-compile your_contract.cairo --single-file your_contract.json``` - Add a test for your contract calling ```test_contract``` passing: - Your compiled contract path - The entrypoint you are wanting to execute - The parameters needed in order to call that entrypoint - - The expected returned value + - The expected returned value diff --git a/examples/contract_execution/example_contract.cairo b/examples/contract_execution/example_contract.cairo deleted file mode 100644 index 1fe12c14a..000000000 --- a/examples/contract_execution/example_contract.cairo +++ /dev/null @@ -1,32 +0,0 @@ -// Declare this file as a StarkNet contract. -%lang starknet - -from starkware.cairo.common.cairo_builtins import HashBuiltin - -// Define a storage variable. -@storage_var -func balance() -> (res: felt) { -} - -// Increases the balance by the given amount. -@external -func increase_balance{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, -}(amount: felt) { - let (res) = balance.read(); - balance.write(res + amount); - return (); -} - -// Returns the current balance. -@view -func get_balance{ - syscall_ptr: felt*, - pedersen_ptr: HashBuiltin*, - range_check_ptr, -}() -> (res: felt) { - let (res) = balance.read(); - return (res=res); -} diff --git a/examples/contract_execution/execute_contract.rs b/examples/contract_execution/execute_contract.rs deleted file mode 100644 index 2fa36970a..000000000 --- a/examples/contract_execution/execute_contract.rs +++ /dev/null @@ -1,136 +0,0 @@ -#![deny(warnings)] - -use cairo_vm::felt::Felt252; -use starknet_in_rust::{ - execution::{ - execution_entry_point::ExecutionEntryPoint, - objects::{CallInfo, CallType, TransactionExecutionContext}, - }, - state::{ - contract_state::ContractState, in_memory_state_reader::InMemoryStateReader, - structs::ExecutionResourcesManager, - }, - state::cached_state::CachedState, - definitions::{ - constants::TRANSACTION_VERSION, - block_context::BlockContext, - }, - services::api::contract_class::{ContractClass, EntryPointType}, - utils::{calculate_sn_keccak, Address}, -}; -use std::path::Path; - -fn test_contract( - contract_path: impl AsRef, - entry_point: &str, - call_data: Vec, - return_data: impl Into>, -) { - let contract_class = ContractClass::try_from(contract_path.as_ref().to_path_buf()) - .expect("Could not load contract from JSON"); - - - - //* -------------------------------------------- - //* Create a default contract data - //* -------------------------------------------- - - let contract_address = Address(1111.into()); - let class_hash = [1; 32]; - - //* -------------------------------------------- - //* Create default context - //* -------------------------------------------- - - let block_context = BlockContext::default(); - - let tx_execution_context = - TransactionExecutionContext::create_for_testing( - Address(0.into()), - 10, - 0.into(), - block_context.invoke_tx_max_n_steps(), - TRANSACTION_VERSION, - ); - - //* -------------------------------------------- - //* Create starknet state with the contract - //* (This would be the equivalent of - //* declaring and deploying the contract) - //* ------------------------------------------- - - let contract_state = ContractState::new( - class_hash, - tx_execution_context.nonce(), - Default::default(), - ); - let mut state_reader = InMemoryStateReader::new(HashMap::new(), HashMap::new()); - state_reader - .contract_states_mut() - .insert(contract_address, contract_state); - - let mut state = CachedState::new( - state_reader, - Some([(class_hash, contract_class)].iter().collect()), - ); - - //* ------------------------------------ - //* Create execution entry point - //* ------------------------------------ - - let caller_address = Address(0.into()); - - let entry_point_selector = Felt252::from_bytes_be(&calculate_sn_keccak(entry_point.as_bytes())); - let entry_point = ExecutionEntryPoint::new( - contract_address, - call_data, - entry_point_selector, - caller_address, - EntryPointType::External, - CallType::Delegate.into(), - class_hash.into(), - ); - - let mut resources_manager = ExecutionResourcesManager::default(); - - assert_eq!( - entry_point - .execute( - &mut state, - &block_context, - &mut resources_manager, - &tx_execution_context, - ) - .expect("Could not execute contract"), - CallInfo { - contract_address, - caller_address, - entry_point_type: EntryPointType::External.into(), - call_type: CallType::Delegate.into(), - class_hash: class_hash.into(), - entry_point_selector: Some(entry_point_selector), - calldata: call_data.into(), - retdata: return_data.into(), - ..Default::default() - }, - ); -} - -#[test] -fn test_fibonacci(){ - test_contract( - "starknet_programs/fibonacci.json", - "fib", - [1.into(), 1.into(), 10.into()].to_vec(), - [144.into()].to_vec(), - ); -} - -#[test] -fn test_factorial(){ - test_contract("starknet_programs/factorial.json", - "factorial", - [10.into()].to_vec(), - [3628800.into()].to_vec() - ); -} diff --git a/examples/contract_execution/main.rs b/examples/contract_execution/main.rs deleted file mode 100644 index a82aa4f9a..000000000 --- a/examples/contract_execution/main.rs +++ /dev/null @@ -1,172 +0,0 @@ -#![deny(warnings)] - -//! A simple example of starknet-rs use. -//! -//! In [`test_contract`] we have all the interaction with the crate's API. -//! In [`main`] we use it to run a compiled contract's entrypoint and print -//! the returned data. -//! -//! It also includes some small tests that assert the data returned by -//! running some pre-compiled contracts is as expected. - -use cairo_vm::felt::Felt252; -use starknet_in_rust::{ - definitions::{block_context::BlockContext, constants::TRANSACTION_VERSION}, - services::api::contract_classes::{ - compiled_class::CompiledClass, deprecated_contract_class::ContractClass, - }, - state::{ - cached_state::CachedState, in_memory_state_reader::InMemoryStateReader, state_api::State, - }, - transaction::{Declare, Deploy, InvokeFunction, Transaction}, - utils::{calculate_sn_keccak, Address}, -}; -use std::{collections::HashMap, path::Path, sync::Arc}; -use tracing_subscriber::EnvFilter; - -fn main() { - tracing::subscriber::set_global_default( - tracing_subscriber::FmtSubscriber::builder() - .with_env_filter(EnvFilter::from_default_env()) - .finish(), - ) - .unwrap(); - - // replace this with the path to your compiled contract - let contract_path = "starknet_programs/fibonacci.json"; - - // replace this with the name of your entrypoint - let entry_point: &str = "fib"; - - // replace this with the arguments for the entrypoint - let calldata: Vec = [10.into()].to_vec(); - - let retdata = test_contract(contract_path, entry_point, calldata); - - let result_strs: Vec = retdata.iter().map(Felt252::to_string).collect(); - let joined_str = result_strs.join(", "); - - println!("The returned values were: {joined_str}"); -} - -/// This function: -/// - declares a new contract class -/// - deploys a new contract -/// - executes the given entry point in the deployed contract -fn test_contract( - contract_path: impl AsRef, - entry_point: &str, - call_data: Vec, -) -> Vec { - //* -------------------------------------------- - //* Initialize needed variables - //* -------------------------------------------- - let block_context = BlockContext::default(); - let chain_id = block_context.starknet_os_config().chain_id().clone(); - let sender_address = Address(1.into()); - let signature = vec![]; - - //* -------------------------------------------- - //* Initialize state - //* -------------------------------------------- - let state_reader = Arc::new(InMemoryStateReader::default()); - let mut state = CachedState::new(state_reader, HashMap::new()); - - //* -------------------------------------------- - //* Read contract from file - //* -------------------------------------------- - let contract_class = - ContractClass::from_path(contract_path).expect("Could not load contract from JSON"); - - //* -------------------------------------------- - //* Declare new contract class - //* -------------------------------------------- - let declare_tx = Declare::new( - contract_class.clone(), - chain_id.clone(), - sender_address, - 0, // max fee - 0.into(), - signature.clone(), - 0.into(), // nonce - ) - .expect("couldn't create declare transaction"); - - declare_tx - .execute(&mut state, &block_context) - .expect("could not declare the contract class"); - - //* -------------------------------------------- - //* Deploy new contract class instance - //* -------------------------------------------- - - let deploy = Deploy::new( - Default::default(), // salt - contract_class.clone(), - vec![], // call data - block_context.starknet_os_config().chain_id().clone(), - TRANSACTION_VERSION.clone(), - ) - .unwrap(); - - state - .set_contract_class( - &deploy.contract_hash, - &CompiledClass::Deprecated(Arc::new(contract_class)), - ) - .unwrap(); - let contract_address = deploy.contract_address.clone(); - - let tx = Transaction::Deploy(deploy); - - tx.execute(&mut state, &block_context, 0) - .expect("could not deploy contract"); - - //* -------------------------------------------- - //* Execute contract entrypoint - //* -------------------------------------------- - let entry_point_selector = Felt252::from_bytes_be(&calculate_sn_keccak(entry_point.as_bytes())); - - let invoke_tx = InvokeFunction::new( - contract_address, - entry_point_selector, - 0, - TRANSACTION_VERSION.clone(), - call_data, - signature, - chain_id, - Some(0.into()), - ) - .unwrap(); - - let tx = Transaction::InvokeFunction(invoke_tx); - let tx_exec_info = tx.execute(&mut state, &block_context, 0).unwrap(); - - //* -------------------------------------------- - //* Extract return values - //* -------------------------------------------- - tx_exec_info - .call_info - .expect("call info should exist") - .retdata -} - -#[test] -fn test_fibonacci() { - let retdata = test_contract( - "starknet_programs/fibonacci.json", - "fib", - [1.into(), 1.into(), 10.into()].to_vec(), - ); - assert_eq!(retdata, vec![144.into()]); -} - -#[test] -fn test_factorial() { - let retdata = test_contract( - "starknet_programs/factorial.json", - "factorial", - [10.into()].to_vec(), - ); - assert_eq!(retdata, vec![3628800.into()]); -} diff --git a/examples/contract_execution/src/main.rs b/examples/contract_execution/src/main.rs new file mode 100644 index 000000000..3b8c742b7 --- /dev/null +++ b/examples/contract_execution/src/main.rs @@ -0,0 +1,266 @@ +#![deny(warnings)] + +//! A simple example of starknet-rs use. +//! +//! In [`test_contract`] we have all the interaction with the crate's API. +//! In [`main`] we use it to run a compiled contract's entrypoint and print +//! the returned data. +//! +//! It also includes some small tests that assert the data returned by +//! running some pre-compiled contracts is as expected. + +use cairo_vm::felt::{felt_str, Felt252}; +use starknet_in_rust::{ + core::contract_address::{compute_casm_class_hash, compute_deprecated_class_hash}, + definitions::block_context::BlockContext, + services::api::contract_classes::{ + compiled_class::CompiledClass, deprecated_contract_class::ContractClass, + }, + state::{ + cached_state::CachedState, in_memory_state_reader::InMemoryStateReader, state_api::State, + }, + transaction::{DeclareV2, DeployAccount, InvokeFunction}, + utils::{calculate_sn_keccak, felt_to_hash, Address}, + CasmContractClass, SierraContractClass, +}; +use std::{collections::HashMap, fs::File, io::BufReader, path::Path, str::FromStr, sync::Arc}; + +fn main() { + // replace this with the path to your compiled contract + let contract_path = "../../starknet_programs/cairo2/fibonacci.sierra"; + + // replace this with the name of your entrypoint + let entry_point: &str = "fib"; + + // replace this with the arguments for the entrypoint + let calldata: Vec = [1.into(), 1.into(), 10.into()].to_vec(); + + let retdata = test_contract(contract_path, entry_point, calldata); + + let result_strs: Vec = retdata.iter().map(Felt252::to_string).collect(); + let joined_str = result_strs.join(", "); + + println!("The returned values were: {joined_str}"); +} + +/// This function: +/// - deploys an account +/// - declares a new contract class +/// - deploys a new contract +/// - executes the given entry point in the deployed contract +fn test_contract( + contract_path: impl AsRef, + entry_point: &str, + call_data: Vec, +) -> Vec { + //* -------------------------------------------- + //* Initialize needed variables + //* -------------------------------------------- + let block_context = BlockContext::default(); + let chain_id = block_context.starknet_os_config().chain_id().clone(); + // Values hardcoded to pass signature validation + let signature = vec![ + felt_str!("3086480810278599376317923499561306189851900463386393948998357832163236918254"), + felt_str!("598673427589502599949712887611119751108407514580626464031881322743364689811"), + ]; + + //* -------------------------------------------- + //* Initialize state + //* -------------------------------------------- + let state_reader = Arc::new(InMemoryStateReader::default()); + let mut state = CachedState::new(state_reader, HashMap::new()); + + //* -------------------------------------------- + //* Deploy deployer contract + //* -------------------------------------------- + let deployer_contract = + ContractClass::from_str(include_str!("../../../starknet_programs/deployer.json")).unwrap(); + let deployer_contract_address = Address(Felt252::from(17)); + let deployer_contract_class_hash = + felt_to_hash(&compute_deprecated_class_hash(&deployer_contract).unwrap()); + state + .set_contract_class( + &deployer_contract_class_hash, + &CompiledClass::Deprecated(Arc::new(deployer_contract)), + ) + .unwrap(); + state + .deploy_contract( + deployer_contract_address.clone(), + deployer_contract_class_hash, + ) + .expect("Failed to deploy deployer contract"); + + //* -------------------------------------------- + //* Deploy Account contract + //* -------------------------------------------- + let account_contract = + ContractClass::from_str(include_str!("../../../starknet_programs/Account.json")).unwrap(); + let account_contract_class_hash = felt_to_hash(&Felt252::from(1)); + state + .set_contract_class( + &account_contract_class_hash, + &CompiledClass::Deprecated(Arc::new(account_contract)), + ) + .unwrap(); + + let internal_deploy = DeployAccount::new( + account_contract_class_hash, + 0, + 0.into(), + 0.into(), + // Values hardcoded to pass signature validation + vec![felt_str!( + "1735102664668487605176656616876767369909409133946409161569774794110049207117" + )], + signature.clone(), + felt_str!("2669425616857739096022668060305620640217901643963991674344872184515580705509"), + chain_id.clone(), + ) + .unwrap(); + + let account_contract_address = internal_deploy + .execute(&mut state, &block_context) + .expect("Account Deploy Failed") + .call_info + .unwrap() + .contract_address + .clone(); + + //* -------------------------------------------- + //* Read contract from file + //* -------------------------------------------- + let file = File::open(contract_path).unwrap(); + let reader = BufReader::new(file); + let sierra_contract_class: SierraContractClass = + serde_json::from_reader(reader).expect("Could not load contract from JSON"); + let casm_class = + CasmContractClass::from_contract_class(sierra_contract_class.clone(), false).unwrap(); + let compiled_class_hash = + compute_casm_class_hash(&casm_class).expect("Error computing sierra class hash"); + //* -------------------------------------------- + //* Declare new contract class + //* -------------------------------------------- + let declare_tx = DeclareV2::new_with_tx_hash( + &sierra_contract_class, + Some(casm_class), + compiled_class_hash.clone(), + account_contract_address.clone(), + 0, // max fee + 1.into(), + signature.clone(), + 0.into(), // nonce + // Value hardcoded to pass signature validation + 2718.into(), + ) + .expect("couldn't create declare transaction"); + + declare_tx + .execute(&mut state, &block_context) + .expect("could not declare the contract class"); + + //* ---------------------------------------------------------- + //* Deploy new contract class instance through the deployer + //* ----------------------------------------------------------- + + let deploy = InvokeFunction::new( + deployer_contract_address, + Felt252::from_bytes_be(&calculate_sn_keccak("deploy_contract".as_bytes())), + 0, + 0.into(), + vec![compiled_class_hash, 3.into(), 0.into()], // call data + signature.clone(), + block_context.starknet_os_config().chain_id().clone(), + None, + ) + .unwrap(); + + let contract_address = deploy + .execute(&mut state, &block_context, 0) + .expect("could not deploy contract") + .call_info + .unwrap() + .retdata[0] + .clone(); + + //* --------------------------------------------------------- + //* Execute contract entrypoint through the account + //* --------------------------------------------------------- + let entry_point_selector = Felt252::from_bytes_be(&calculate_sn_keccak(entry_point.as_bytes())); + let mut account_execute_calldata = vec![ + // call_array_len: felt + 1.into(), + // call_array: CallArray* + // struct CallArray { + // to: felt, + contract_address, + // selector: felt, + entry_point_selector, + // data_offset: felt, + 0.into(), + // data_len: felt, + call_data.len().into(), + // } + // calldata_len: felt + call_data.len().into(), + ]; + // calldata: felt* + account_execute_calldata.extend(call_data); + let invoke_tx = InvokeFunction::new_with_tx_hash( + account_contract_address, + Felt252::from_bytes_be(&calculate_sn_keccak("__execute__".as_bytes())), + 0, + 1.into(), + account_execute_calldata, + signature, + Some(1.into()), + // Value hardcoded to pass signature validation + 2718.into(), + ) + .unwrap(); + + let tx_exec_info = invoke_tx.execute(&mut state, &block_context, 0).unwrap(); + + //* -------------------------------------------- + //* Extract return values + //* -------------------------------------------- + tx_exec_info + .call_info + .expect("call info should exist") + .retdata +} + +#[cfg(test)] +mod tests { + use crate::test_contract; + + #[test] + fn test_example_contract() { + let retdata = test_contract( + "../../starknet_programs/cairo2/example_contract.sierra", + "get_balance", + [].to_vec(), + ); + assert_eq!(retdata, vec![0.into()]); + } + + #[test] + fn test_fibonacci() { + let retdata = test_contract( + "../../starknet_programs/cairo2/fibonacci.sierra", + "fib", + [1.into(), 1.into(), 10.into()].to_vec(), + ); + assert_eq!(retdata, vec![89.into()]); + } + + #[test] + fn test_factorial() { + let retdata = test_contract( + "../../starknet_programs/cairo2/factorial.sierra", + "factorial", + [10.into()].to_vec(), + ); + assert_eq!(retdata, vec![3628800.into()]); + } +} diff --git a/rpc_state_reader/Cargo.toml b/rpc_state_reader/Cargo.toml index 0cdbbf8ef..de58a9736 100644 --- a/rpc_state_reader/Cargo.toml +++ b/rpc_state_reader/Cargo.toml @@ -20,7 +20,7 @@ thiserror = { workspace = true } flate2 = "1.0.25" serde_with = "3.0.0" dotenv = "0.15.0" -cairo-vm = "0.8.5" +cairo-vm = { workspace = true } blockifier = "=0.2.0-rc0" starknet_in_rust = { path = "../", version = "0.4.0" } diff --git a/starknet_programs/cairo2/example_contract.cairo b/starknet_programs/cairo2/example_contract.cairo new file mode 100644 index 000000000..bcb981da0 --- /dev/null +++ b/starknet_programs/cairo2/example_contract.cairo @@ -0,0 +1,32 @@ +#[starknet::interface] +trait IExampleContract { + fn get_balance(ref self: TContractState) -> u128; + fn increase_balance(ref self: TContractState, amount: u128); +} + +#[starknet::contract] +mod ExampleContract { + use traits::Into; + use starknet::info::get_contract_address; + + #[storage] + struct Storage { + balance: u128, + } + + #[constructor] + fn constructor(ref self: ContractState) { + } + + #[external(v0)] + impl ExampleContract of super::IExampleContract { + fn get_balance(ref self: ContractState) -> u128 { + self.balance.read() + } + + fn increase_balance(ref self: ContractState, amount: u128) { + let balance = self.balance.read(); + self.balance.write(balance + amount); + } + } +} diff --git a/starknet_programs/deployer.cairo b/starknet_programs/deployer.cairo new file mode 100644 index 000000000..a54a66098 --- /dev/null +++ b/starknet_programs/deployer.cairo @@ -0,0 +1,49 @@ +// Code taken from Universal Deployer Proposal in starknet forum https://community.starknet.io/t/universal-deployer-contract-proposal/1864 +%lang starknet + +from starkware.starknet.common.syscalls import get_caller_address, deploy +from starkware.cairo.common.cairo_builtins import HashBuiltin +from starkware.cairo.common.hash import hash2 +from starkware.cairo.common.bool import FALSE + +@event +func ContractDeployed( + contractAddress: felt, + deployer: felt, + classHash: felt, + salt: felt + ){ + } + +@external +func deploy_contract{ + syscall_ptr: felt*, + pedersen_ptr: HashBuiltin*, + range_check_ptr, + } ( + class_hash: felt, + salt: felt, + constructor_calldata_len: felt, + constructor_calldata: felt*, + ) -> (contract_address: felt){ + + let (deployer) = get_caller_address(); + let (unique_salt) = hash2{hash_ptr=pedersen_ptr}(deployer, salt); + + let (contract_address) = deploy( + class_hash=class_hash, + contract_address_salt=unique_salt, + constructor_calldata_size=constructor_calldata_len, + constructor_calldata=constructor_calldata, + deploy_from_zero=FALSE + ); + + ContractDeployed.emit( + contractAddress=contract_address, + deployer=deployer, + classHash=class_hash, + salt=salt + ); + + return (contract_address=contract_address); +}