Skip to content

Commit

Permalink
Implement deploy in execution layer (#828)
Browse files Browse the repository at this point in the history
<!-- Reference any GitHub issues resolved by this PR -->

Closes #805 

## Introduced changes

## Breaking changes

<!-- List of all breaking changes, if applicable -->

## Checklist

<!-- Make sure all of these are complete -->

- [ ] Linked relevant issue
- [ ] Updated relevant documentation
- [ ] Added relevant tests
- [ ] Performed self-review of the code
- [ ] Added changes to `CHANGELOG.md`

---------

Co-authored-by: Piotr Magiera <56825108+piotmag769@users.noreply.github.com>
  • Loading branch information
Arcticae and piotmag769 authored Oct 10, 2023
1 parent 2c81cbd commit bd93405
Show file tree
Hide file tree
Showing 17 changed files with 440 additions and 365 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Spying events interface is updated to enable the use of events defined inside contracts in assertions
- Fixed inconsistent pointers bug https://github.com/foundry-rs/starknet-foundry/issues/659
- Fixed an issue where `deploy_at` would not trigger the constructors https://github.com/foundry-rs/starknet-foundry/issues/805

## [0.7.1] - 2023-09-27

Expand Down
229 changes: 68 additions & 161 deletions crates/cheatnet/src/cheatcodes/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
use crate::constants::TEST_ACCOUNT_CONTRACT_ADDRESS;
use crate::constants as crate_constants;
use crate::constants::{build_block_context, build_transaction_context};
use crate::state::BlockifierState;
use crate::{cheatcodes::EnhancedHintError, CheatnetState};
use crate::CheatnetState;
use anyhow::Result;
use blockifier::abi::abi_utils::selector_from_name;
use blockifier::execution::execution_utils::{felt_to_stark_felt, stark_felt_to_felt};
use blockifier::transaction::constants::EXECUTE_ENTRY_POINT_NAME;
use blockifier::abi::constants as blockifier_constants;
use blockifier::execution::entry_point::{
ConstructorContext, EntryPointExecutionContext, ExecutionResources,
};
use blockifier::execution::execution_utils::felt_to_stark_felt;
use std::sync::Arc;

use blockifier::state::state_api::State;
use cairo_felt::Felt252;
use cairo_lang_runner::short_string::as_cairo_short_string;
use cairo_vm::vm::errors::hint_errors::HintError::CustomHint;
use conversions::StarknetConversions;
use num_traits::ToPrimitive;

use starknet::core::utils::get_selector_from_name;
use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector, PatriciaKey};
use starknet_api::hash::{StarkFelt, StarkHash};
use starknet_api::transaction::ContractAddressSalt;
use starknet_api::{patricia_key, stark_felt};
use crate::cheatcodes::EnhancedHintError;
use crate::execution::syscalls::execute_deployment;
use starknet_api::core::{ClassHash, ContractAddress};
use starknet_api::transaction::Calldata;

use super::CheatcodeError;
use crate::rpc::{call_contract, CallContractOutput, ResourceReport};
use crate::rpc::{CallContractFailure, ResourceReport};

#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, PartialEq)]
pub struct DeployPayload {
pub contract_address: ContractAddress,
#[derive(Debug)]
pub struct DeployCallPayload {
pub resource_report: ResourceReport,
pub contract_address: ContractAddress,
}

#[allow(clippy::module_name_repetitions)]
Expand All @@ -35,15 +38,9 @@ pub fn deploy_at(
class_hash: &ClassHash,
calldata: &[Felt252],
contract_address: ContractAddress,
) -> Result<DeployPayload, CheatcodeError> {
let salt = cheatnet_state.get_salt();
cheatnet_state.increment_deploy_salt_base();

// Deploy a contract using syscall deploy.
let account_address = ContractAddress(patricia_key!(TEST_ACCOUNT_CONTRACT_ADDRESS));
let entry_point_selector = selector_from_name("deploy_contract");
) -> Result<DeployCallPayload, CheatcodeError> {
let blockifier_state_raw: &mut dyn State = blockifier_state.blockifier_state;

let blockifier_state_raw: &mut dyn State = blockifier_state.blockifier_state as &mut dyn State;
if let Ok(class_hash) = blockifier_state_raw.get_class_hash_at(contract_address) {
if class_hash != ClassHash::default() {
return Err(CheatcodeError::Unrecoverable(EnhancedHintError::from(
Expand All @@ -52,72 +49,57 @@ pub fn deploy_at(
}
}

let execute_calldata = create_execute_calldata(
calldata,
class_hash,
&account_address,
&entry_point_selector,
&salt,
let entry_point_execution_ctx = &mut EntryPointExecutionContext::new(
build_block_context(),
build_transaction_context(),
blockifier_constants::MAX_STEPS_PER_TX,
);

let call_result = call_contract(
blockifier_state,
let ctor_context = ConstructorContext {
class_hash: *class_hash,
code_address: Some(contract_address),
storage_address: contract_address,
caller_address: crate_constants::TEST_ADDRESS
.to_string()
.to_contract_address(),
};

let calldata = Calldata(Arc::new(
calldata.to_vec().iter().map(felt_to_stark_felt).collect(),
));

let resources = &mut ExecutionResources::default();

let result = execute_deployment(
blockifier_state_raw,
resources,
entry_point_execution_ctx,
ctor_context,
calldata,
u64::MAX,
cheatnet_state,
&account_address,
&get_selector_from_name(EXECUTE_ENTRY_POINT_NAME)
.unwrap()
.to_felt252(),
execute_calldata.as_slice(),
)
.unwrap_or_else(|err| panic!("Deploy txn failed: {err}"));

let blockifier_state_raw = &mut blockifier_state.blockifier_state;

match call_result {
CallContractOutput::Success {
resource_report, ..
} => {
let result = blockifier_state_raw
.set_class_hash_at(contract_address, *class_hash)
.map(|()| contract_address)
.map_err(|msg| {
CheatcodeError::Unrecoverable(EnhancedHintError::from(CustomHint(Box::from(
msg.to_string(),
))))
});

match result {
Ok(contract_address) => Ok(DeployPayload {
contract_address,
resource_report,
}),
Err(cheatcode_error) => Err(cheatcode_error),
}
}
CallContractOutput::Panic { panic_data, .. } => {
let panic_data_str = panic_data
.iter()
.map(|x| as_cairo_short_string(x).unwrap())
.collect::<Vec<String>>()
.join("\n");

for invalid_calldata_msg in [
"Failed to deserialize param #",
"Input too long for arguments",
] {
if panic_data_str.contains(invalid_calldata_msg) {
return Err(CheatcodeError::Unrecoverable(EnhancedHintError::from(
CustomHint(Box::from(panic_data_str)),
)));
}
}

Err(CheatcodeError::Recoverable(panic_data))
}
CallContractOutput::Error { msg, .. } => Err(CheatcodeError::Unrecoverable(
EnhancedHintError::from(CustomHint(Box::from(msg))),
)),
}
.map(|call_info| DeployCallPayload {
resource_report: ResourceReport::new(
call_info
.execution
.gas_consumed
.to_f64()
.expect("Conversion to f64 failed")
+ blockifier_constants::DEPLOY_GAS_COST
.to_f64()
.expect("Conversion to f64 failed"),
resources,
),
contract_address,
})
.map_err(|err| {
let call_contract_failure =
CallContractFailure::from_execution_error(&err, &contract_address);
CheatcodeError::from(call_contract_failure)
});
cheatnet_state.increment_deploy_salt_base();
result
}

#[allow(clippy::module_name_repetitions)]
Expand All @@ -126,7 +108,7 @@ pub fn deploy(
cheatnet_state: &mut CheatnetState,
class_hash: &ClassHash,
calldata: &[Felt252],
) -> Result<DeployPayload, CheatcodeError> {
) -> Result<DeployCallPayload, CheatcodeError> {
let contract_address = cheatnet_state.precalculate_address(class_hash, calldata);

deploy_at(
Expand All @@ -137,78 +119,3 @@ pub fn deploy(
contract_address,
)
}

fn create_execute_calldata(
calldata: &[Felt252],
class_hash: &ClassHash,
account_address: &ContractAddress,
entry_point_selector: &EntryPointSelector,
salt: &ContractAddressSalt,
) -> Vec<Felt252> {
let calldata_len = u128::try_from(calldata.len()).unwrap();
let mut execute_calldata = vec![
*account_address.0.key(), // Contract address.
entry_point_selector.0, // EP selector.
stark_felt!(calldata_len + 3), // Calldata length.
class_hash.0, // Calldata: class_hash.
salt.0, // Contract_address_salt.
stark_felt!(calldata_len), // Constructor calldata length.
];
let mut calldata: Vec<StarkFelt> = calldata.iter().map(felt_to_stark_felt).collect();
execute_calldata.append(&mut calldata);
return execute_calldata
.iter()
.map(|sf| stark_felt_to_felt(*sf))
.collect();
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn execute_calldata() {
let calldata = create_execute_calldata(
&[Felt252::from(100), Felt252::from(200)],
&ClassHash(StarkFelt::from(123_u32)),
&ContractAddress::try_from(StarkFelt::from(111_u32)).unwrap(),
&EntryPointSelector(StarkFelt::from(222_u32)),
&ContractAddressSalt(StarkFelt::from(333_u32)),
);
assert_eq!(
calldata,
vec![
Felt252::from(111_u32),
Felt252::from(222_u32),
Felt252::from(5_u32),
Felt252::from(123_u32),
Felt252::from(333_u32),
Felt252::from(2_u32),
Felt252::from(100_u32),
Felt252::from(200_u32),
]
);
}

#[test]
fn execute_calldata_no_entrypoint_calldata() {
let calldata = create_execute_calldata(
&[],
&ClassHash(StarkFelt::from(123_u32)),
&ContractAddress::try_from(StarkFelt::from(111_u32)).unwrap(),
&EntryPointSelector(StarkFelt::from(222_u32)),
&ContractAddressSalt(StarkFelt::from(333_u32)),
);
assert_eq!(
calldata,
vec![
Felt252::from(111_u32),
Felt252::from(222_u32),
Felt252::from(3_u32),
Felt252::from(123_u32),
Felt252::from(333_u32),
Felt252::from(0_u32),
]
);
}
}
13 changes: 13 additions & 0 deletions crates/cheatnet/src/cheatcodes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::io;

use crate::rpc::CallContractFailure;
use blockifier::state::errors::StateError;
use cairo_felt::Felt252;
use cairo_vm::vm::errors::hint_errors::HintError;
Expand Down Expand Up @@ -53,6 +54,7 @@ impl From<EnhancedHintError> for HintError {
}
}

/// A structure used for returning cheatcode errors in tests
#[derive(Debug)]
pub enum CheatcodeError {
Recoverable(Vec<Felt252>), // Return error result in cairo
Expand All @@ -65,6 +67,17 @@ impl From<EnhancedHintError> for CheatcodeError {
}
}

impl From<CallContractFailure> for CheatcodeError {
fn from(value: CallContractFailure) -> Self {
match value {
CallContractFailure::Panic { panic_data } => CheatcodeError::Recoverable(panic_data),
CallContractFailure::Error { msg } => {
CheatcodeError::Unrecoverable(HintError::CustomHint(Box::from(msg)).into())
}
}
}
}

#[derive(Debug, PartialEq, Clone)]
pub struct ContractArtifacts {
pub sierra: String,
Expand Down
14 changes: 7 additions & 7 deletions crates/cheatnet/src/cheatcodes/precalculate_address.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use crate::constants::TEST_ACCOUNT_CONTRACT_ADDRESS;

use crate::CheatnetState;
use blockifier::execution::execution_utils::felt_to_stark_felt;
use cairo_felt::Felt252;
use starknet_api::core::PatriciaKey;
use starknet_api::core::{calculate_contract_address, ClassHash, ContractAddress};
use starknet_api::hash::{StarkFelt, StarkHash};
use starknet_api::hash::StarkFelt;
use starknet_api::transaction::Calldata;

use starknet_api::patricia_key;
use crate::constants as crate_constants;
use conversions::StarknetConversions;

impl CheatnetState {
#[must_use]
Expand All @@ -17,11 +15,13 @@ impl CheatnetState {
class_hash: &ClassHash,
calldata: &[Felt252],
) -> ContractAddress {
let account_address = ContractAddress(patricia_key!(TEST_ACCOUNT_CONTRACT_ADDRESS));
let salt = self.get_salt();

let execute_calldata = create_execute_calldata(calldata);
calculate_contract_address(salt, *class_hash, &execute_calldata, account_address).unwrap()
let deployer_address = crate_constants::TEST_ADDRESS
.to_string()
.to_contract_address();
calculate_contract_address(salt, *class_hash, &execute_calldata, deployer_address).unwrap()
}
}

Expand Down
8 changes: 2 additions & 6 deletions crates/cheatnet/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,10 @@ pub const SECURITY_TEST_CLASS_HASH: &str = "0x114";
pub const TEST_ERC20_CONTRACT_CLASS_HASH: &str = "0x1010";

pub const TEST_CONTRACT_CLASS_HASH: &str = "0x117";
pub const STEP_RESOURCE_COST: f64 = 0.01_f64;
// snforge_std/src/cheatcodes.cairo::test_address
pub const TEST_ADDRESS: &str = "0x01724987234973219347210837402";

pub const STEP_RESOURCE_COST: f64 = 0.01_f64;

// HOW TO FIND:
// 1. https://docs.starknet.io/documentation/architecture_and_concepts/Network_Architecture/fee-mechanism/#general_case
// 2. src/starkware/cairo/lang/instances.py::starknet_with_keccak_instance
Expand Down Expand Up @@ -210,10 +209,7 @@ pub fn build_testing_state(predeployed_contracts: &Utf8PathBuf) -> DictStateRead
let test_contract_class_hash = ClassHash(stark_felt!(TEST_CONTRACT_CLASS_HASH));

let class_hash_to_class = HashMap::from([
(
test_account_class_hash,
ContractClass::V1(account_class.clone()),
),
(test_account_class_hash, ContractClass::V1(account_class)),
// This is dummy put here only to satisfy blockifier
// this class is not used and the test contract cannot be called
(test_contract_class_hash, contract_class_no_entrypoints()),
Expand Down
Loading

0 comments on commit bd93405

Please sign in to comment.