Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix predeployed account constructor simulation #431

Merged
merged 8 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
- [ ] Performed code self-review
- [ ] Rebased to the latest commit of the target branch (or merged it into my branch)
- [ ] Updated the docs if needed, including the [TODO section](https://github.com/0xSpaceShard/starknet-devnet-rs/?tab=readme-ov-file#todo-to-reach-feature-parity-with-the-pythonic-devnet)
- [ ] Linked the issues which this PR resolves
- [ ] Linked the [issues](https://github.com/0xSpaceShard/starknet-devnet-rs/issues) which this PR resolves
- [ ] Updated the tests if needed; all passing ([execution info](https://github.com/0xSpaceShard/starknet-devnet-rs/?tab=readme-ov-file#test-execution))
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,8 @@ Devnet exposes the following endpoints:

Tests in devnet require an erc20 contract with the `Mintable` feature, keep in mind that before the compilation process of [cairo-contracts](https://github.com/OpenZeppelin/cairo-contracts/) you need to mark the `Mintable` check box in this [wizard](https://wizard.openzeppelin.com/cairo) and copy this implementation to `/src/presets/erc20.cairo`.

If smart contract constructor logic has changed, Devnet's predeployment logic needs to be changed, e.g. `simulate_constructor` in `crates/starknet-devnet-core/src/account.rs`.
mikiw marked this conversation as resolved.
Show resolved Hide resolved

### Development - Updating Starknet

Updating the underlying Starknet is done by updating the `blockifier` dependency. It also requires updating the `STARKNET_VERSION` constant.
Expand Down
35 changes: 27 additions & 8 deletions crates/starknet-devnet-core/src/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use starknet_types::traits::HashProducer;

use crate::constants::{
CAIRO_0_ACCOUNT_CONTRACT, CHARGEABLE_ACCOUNT_ADDRESS, CHARGEABLE_ACCOUNT_PRIVATE_KEY,
CHARGEABLE_ACCOUNT_PUBLIC_KEY,
CHARGEABLE_ACCOUNT_PUBLIC_KEY, ISRC6_ID_HEX,
};
use crate::error::DevnetResult;
use crate::state::state_readers::DictState;
Expand Down Expand Up @@ -102,25 +102,44 @@ impl Account {

Ok(ContractAddress::from(account_address))
}
}

impl Deployed for Account {
fn deploy(&self, state: &mut StarknetState) -> DevnetResult<()> {
self.declare_if_undeclared(state, self.class_hash, &self.contract_class)?;
// simulate constructor logic (register interfaces and set public key), as done in
// https://github.com/OpenZeppelin/cairo-contracts/blob/89a450a88628ec3b86273f261b2d8d1ca9b1522b/src/account/account.cairo#L207-L211
fn simulate_constructor(&self, state: &mut StarknetState) -> DevnetResult<()> {
let core_address = self.account_address.try_into()?;

state.predeploy_contract(self.account_address, self.class_hash)?;
let interface_storage_var = get_storage_var_address(
"SRC5_supported_interfaces",
&[Felt::from_prefixed_hex_str(ISRC6_ID_HEX)?],
)?;
state.state.state.set_storage_at(
core_address,
interface_storage_var.try_into()?,
StarkFelt::ONE,
)?;

// set public key directly in the most underlying state
let public_key_storage_var = get_storage_var_address("Account_public_key", &[])?;
state.state.state.set_storage_at(
self.account_address.try_into()?,
core_address,
public_key_storage_var.try_into()?,
self.public_key.into(),
)?;

Ok(())
}
}

impl Deployed for Account {
fn deploy(&self, state: &mut StarknetState) -> DevnetResult<()> {
self.declare_if_undeclared(state, self.class_hash, &self.contract_class)?;

state.predeploy_contract(self.account_address, self.class_hash)?;

// set balance directly in the most underlying state
self.set_initial_balance(&mut state.state.state)?;

self.simulate_constructor(state)?;

Ok(())
}

Expand Down
3 changes: 3 additions & 0 deletions crates/starknet-devnet-core/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ pub const UDC_CONTRACT_CLASS_HASH: &str =
pub const UDC_CONTRACT_ADDRESS: &str =
"0x41A78E741E5AF2FEC34B695679BC6891742439F7AFB8484ECD7766661AD02BF";

/// https://github.com/OpenZeppelin/cairo-contracts/blob/89a450a88628ec3b86273f261b2d8d1ca9b1522b/src/account/interface.cairo#L7
pub const ISRC6_ID_HEX: &str = "0x2ceccef7f994940b3962a6c67e0ba4fcd37df7d131417c604f91e03caecc1cd";

pub const STARKNET_VERSION: &str = "0.13.1.1";

/// ERC20 contracts storage variables
Expand Down
2 changes: 2 additions & 0 deletions crates/starknet-devnet-core/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ pub trait CustomState {
class_hash: ClassHash,
contract_class: ContractClass,
) -> DevnetResult<()>;

/// Link contract address to class hash
fn predeploy_contract(
&mut self,
contract_address: ContractAddress,
Expand Down
100 changes: 66 additions & 34 deletions crates/starknet-devnet/tests/test_account_selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ mod test_account_selection {
use starknet_rs_contract::ContractFactory;
use starknet_rs_core::types::contract::legacy::LegacyContractClass;
use starknet_rs_core::types::{
BlockId, BlockTag, FieldElement, FunctionCall, MaybePendingTransactionReceipt,
TransactionReceipt,
BlockId, BlockTag, DeployAccountTransactionResult, FieldElement, FunctionCall,
MaybePendingTransactionReceipt, TransactionFinalityStatus, TransactionReceipt,
};
use starknet_rs_core::utils::{
get_selector_from_name, get_udc_deployed_address, UdcUniqueness,
Expand Down Expand Up @@ -79,45 +79,49 @@ mod test_account_selection {
.await;
}

/// Common body for tests defined below
async fn can_deploy_new_account_test_body(devnet_args: &[&str]) {
let devnet = BackgroundDevnet::spawn_with_additional_args(devnet_args).await.unwrap();

/// Utility for deploying accounts of `class_hash`
async fn deploy_account(
devnet: &BackgroundDevnet,
class_hash: FieldElement,
) -> DeployAccountTransactionResult {
let signer = get_deployable_account_signer();

let account_factory = OpenZeppelinAccountFactory::new(
FieldElement::from_hex_be(CAIRO_1_ACCOUNT_CONTRACT_SIERRA_HASH).unwrap(),
CHAIN_ID,
signer,
devnet.clone_provider(),
)
.await
.unwrap();
let account_factory =
OpenZeppelinAccountFactory::new(class_hash, CHAIN_ID, signer, devnet.clone_provider())
.await
.unwrap();

let new_account_nonce = FieldElement::ZERO;
let salt = FieldElement::THREE;
let deployment = account_factory
.deploy(salt)
.max_fee(FieldElement::from(1e18 as u128))
.nonce(new_account_nonce);
let new_account_address = deployment.address();
devnet.mint(new_account_address, 1e18 as u128).await;
.nonce(FieldElement::ZERO);
let account_address = deployment.address();
devnet.mint(account_address, 1e18 as u128).await;

let deploy_account_result = deployment.send().await.unwrap();
let account_deployment = deployment.send().await.unwrap();
assert_eq!(account_deployment.contract_address, account_address);
account_deployment
}

/// Common body for tests defined below
async fn can_deploy_new_account_test_body(devnet_args: &[&str]) {
let devnet = BackgroundDevnet::spawn_with_additional_args(devnet_args).await.unwrap();

let class_hash = FieldElement::from_hex_be(CAIRO_1_ACCOUNT_CONTRACT_SIERRA_HASH).unwrap();
let account_deployment = deploy_account(&devnet, class_hash).await;

let deploy_account_receipt = devnet
.json_rpc_client
.get_transaction_receipt(deploy_account_result.transaction_hash)
.get_transaction_receipt(account_deployment.transaction_hash)
.await
.unwrap();

match deploy_account_receipt {
MaybePendingTransactionReceipt::Receipt(TransactionReceipt::DeployAccount(receipt)) => {
assert_eq!(receipt.contract_address, new_account_address);
}
_ => {
panic!("Invalid receipt {:?}", deploy_account_receipt);
assert_eq!(receipt.finality_status, TransactionFinalityStatus::AcceptedOnL2);
}
_ => panic!("Invalid receipt {:?}", deploy_account_receipt),
}
}

Expand All @@ -133,11 +137,8 @@ mod test_account_selection {

#[tokio::test]
async fn can_deploy_new_custom_account() {
can_deploy_new_account_test_body(&[
"--account-class-custom",
CAIRO_1_ACCOUNT_CONTRACT_SIERRA_PATH,
])
.await;
let args = ["--account-class-custom", CAIRO_1_ACCOUNT_CONTRACT_SIERRA_PATH];
can_deploy_new_account_test_body(&args).await;
}

/// Common body for tests defined below
Expand Down Expand Up @@ -220,10 +221,41 @@ mod test_account_selection {

#[tokio::test]
async fn can_declare_deploy_invoke_using_predeployed_custom() {
can_declare_deploy_invoke_using_predeployed_test_body(&[
"--account-class-custom",
CAIRO_1_ACCOUNT_CONTRACT_SIERRA_PATH,
])
.await;
let args = ["--account-class-custom", CAIRO_1_ACCOUNT_CONTRACT_SIERRA_PATH];
can_declare_deploy_invoke_using_predeployed_test_body(&args).await;
}

async fn assert_supports_isrc6(devnet: &BackgroundDevnet, account_address: FieldElement) {
// https://github.com/OpenZeppelin/cairo-contracts/blob/89a450a88628ec3b86273f261b2d8d1ca9b1522b/src/account/interface.cairo#L7
let interface_id_hex = "0x2ceccef7f994940b3962a6c67e0ba4fcd37df7d131417c604f91e03caecc1cd";
let interface_id = FieldElement::from_hex_be(interface_id_hex).unwrap();

let call = FunctionCall {
contract_address: account_address,
entry_point_selector: get_selector_from_name("supports_interface").unwrap(),
calldata: vec![interface_id],
};

let supports =
devnet.json_rpc_client.call(call, BlockId::Tag(BlockTag::Latest)).await.unwrap();
assert_eq!(supports, vec![FieldElement::ONE]);
}

#[tokio::test]
async fn test_interface_support_of_predeployed_account() {
let devnet = BackgroundDevnet::spawn().await.unwrap();
let (_, account_address) = devnet.get_first_predeployed_account().await;

assert_supports_isrc6(&devnet, account_address).await;
}

#[tokio::test]
async fn test_interface_support_of_newly_deployed_account() {
let devnet = BackgroundDevnet::spawn().await.unwrap();

let class_hash = FieldElement::from_hex_be(CAIRO_1_ACCOUNT_CONTRACT_SIERRA_HASH).unwrap();
let account_deployment = deploy_account(&devnet, class_hash).await;

assert_supports_isrc6(&devnet, account_deployment.contract_address).await;
}
}