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

Integrated data transformer with sncast commands #2548

Closed
wants to merge 1 commit into from
Closed
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
46 changes: 33 additions & 13 deletions crates/sncast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use conversions::serde::serialize::CairoSerialize;
use helpers::constants::{KEYSTORE_PASSWORD_ENV_VAR, UDC_ADDRESS};
use rand::rngs::OsRng;
use rand::RngCore;
use response::errors::SNCastStarknetError;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::{Deserializer, Value};
Expand Down Expand Up @@ -270,13 +271,22 @@ pub async fn get_contract_class(
class_hash: Felt,
provider: &JsonRpcClient<HttpTransport>,
) -> Result<ContractClass> {
provider
let result = provider
.get_class(BlockId::Tag(BlockTag::Latest), class_hash)
.await
.map_err(handle_rpc_error)
.context(format!(
"Couldn't retrieve contract class with hash: {class_hash:#x}"
))
.await;

if let Err(ProviderError::StarknetError(ClassHashNotFound)) = result {
// Imitate error thrown on chain to achieve particular error message (Issue #2554)
let artificial_transaction_revert_error = SNCastProviderError::StarknetError(
SNCastStarknetError::ContractError(ContractErrorData {
revert_error: format!("Class with hash {class_hash:#x} is not declared"),
}),
);

return Err(handle_rpc_error(artificial_transaction_revert_error));
}

result.map_err(handle_rpc_error)
}

async fn build_account(
Expand Down Expand Up @@ -468,15 +478,25 @@ pub async fn check_if_legacy_contract(
pub async fn get_class_hash_by_address(
provider: &JsonRpcClient<HttpTransport>,
address: Felt,
) -> Result<Option<Felt>> {
match provider
) -> Result<Felt> {
let result = provider
.get_class_hash_at(BlockId::Tag(Pending), address)
.await
{
Ok(class_hash) => Ok(Some(class_hash)),
Err(StarknetError(ContractNotFound)) => Ok(None),
Err(err) => Err(handle_rpc_error(err)),
.await;

if let Err(ProviderError::StarknetError(ContractNotFound)) = result {
// Imitate error thrown on chain to achieve particular error message (Issue #2554)
let artificial_transaction_revert_error = SNCastProviderError::StarknetError(
SNCastStarknetError::ContractError(ContractErrorData {
revert_error: format!("Requested contract address {address:#x} is not deployed",),
}),
);

return Err(handle_rpc_error(artificial_transaction_revert_error));
}

result.map_err(handle_rpc_error).with_context(|| {
format!("Couldn't retrieve class hash of a contract with address {address:#x}")
})
}

#[must_use]
Expand Down
40 changes: 33 additions & 7 deletions crates/sncast/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::starknet_commands::{
};
use anyhow::{Context, Result};
use configuration::load_global_config;
use data_transformer::Calldata;
use sncast::response::explorer_link::print_block_explorer_link_if_allowed;
use sncast::response::print::{print_command_result, OutputFormat};

Expand All @@ -20,8 +21,8 @@ use sncast::helpers::scarb_utils::{
};
use sncast::response::errors::handle_starknet_command_error;
use sncast::{
chain_id_to_network_name, get_account, get_block_id, get_chain_id, get_default_state_file_name,
NumbersFormat, ValidatedWaitParams, WaitForTx,
chain_id_to_network_name, get_account, get_block_id, get_chain_id, get_class_hash_by_address,
get_contract_class, get_default_state_file_name, NumbersFormat, ValidatedWaitParams, WaitForTx,
};
use starknet::accounts::ConnectedAccount;
use starknet::core::utils::get_selector_from_name;
Expand Down Expand Up @@ -249,9 +250,19 @@ async fn run_async_command(
.try_into_fee_settings(&provider, account.block_id())
.await?;

// safe to unwrap because "constructor" is a standardized name
let selector = get_selector_from_name("constructor").unwrap();

let contract_class = get_contract_class(deploy.class_hash, &provider).await?;
integraledelebesgue marked this conversation as resolved.
Show resolved Hide resolved

let serialized_calldata = constructor_calldata
.map(|data| Calldata::from(data).serialized(contract_class, &selector))
.transpose()?
.unwrap_or_default();

let result = starknet_commands::deploy::deploy(
deploy.class_hash,
&constructor_calldata,
&serialized_calldata,
deploy.salt,
deploy.unique,
fee_settings,
Expand Down Expand Up @@ -283,14 +294,21 @@ async fn run_async_command(
let provider = rpc.get_provider(&config).await?;

let block_id = get_block_id(&block_id)?;
let class_hash = get_class_hash_by_address(&provider, contract_address).await?;
let contract_class = get_contract_class(class_hash, &provider).await?;
integraledelebesgue marked this conversation as resolved.
Show resolved Hide resolved

let entry_point_selector = get_selector_from_name(&function)
let selector = get_selector_from_name(&function)
.context("Failed to convert entry point selector to FieldElement")?;

let serialized_calldata = calldata
.map(|data| Calldata::from(data).serialized(contract_class, &selector))
.transpose()?
.unwrap_or_default();

let result = starknet_commands::call::call(
contract_address,
entry_point_selector,
calldata,
selector,
serialized_calldata,
&provider,
block_id.as_ref(),
)
Expand Down Expand Up @@ -331,9 +349,17 @@ async fn run_async_command(
let selector = get_selector_from_name(&function)
.context("Failed to convert entry point selector to FieldElement")?;

let class_hash = get_class_hash_by_address(&provider, contract_address).await?;
let contract_class = get_contract_class(class_hash, &provider).await?;

let serialized_calldata = calldata
.map(|data| Calldata::from(data).serialized(contract_class, &selector))
.transpose()?
.unwrap_or_default();

let result = starknet_commands::invoke::invoke(
contract_address,
calldata,
serialized_calldata,
nonce,
fee_args,
selector,
Expand Down
4 changes: 2 additions & 2 deletions crates/sncast/src/starknet_commands/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ pub struct Call {
#[clap(short, long)]
pub function: String,

/// Arguments of the called function (list of hex)
/// Arguments of the called function (serialized as a series of felts or written as comma-separated expressions in Cairo syntax)
#[clap(short, long, value_delimiter = ' ', num_args = 1..)]
pub calldata: Vec<Felt>,
pub calldata: Option<Vec<String>>,

/// Block identifier on which call should be performed.
/// Possible values: pending, latest, block hash (0x prefixed string)
Expand Down
4 changes: 2 additions & 2 deletions crates/sncast/src/starknet_commands/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ pub struct Deploy {
#[clap(short = 'g', long)]
pub class_hash: Felt,

/// Calldata for the contract constructor
/// Arguments of the called function (serialized as a series of felts or written as comma-separated expressions in Cairo syntax)
#[clap(short, long, value_delimiter = ' ', num_args = 1..)]
pub constructor_calldata: Vec<Felt>,
pub constructor_calldata: Option<Vec<String>>,

/// Salt for the address
#[clap(short, long)]
Expand Down
4 changes: 2 additions & 2 deletions crates/sncast/src/starknet_commands/invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ pub struct Invoke {
#[clap(short, long)]
pub function: String,

/// Calldata for the invoked function
/// Arguments of the called function (serialized as a series of felts or written as comma-separated expressions in Cairo syntax)
#[clap(short, long, value_delimiter = ' ', num_args = 1..)]
pub calldata: Vec<Felt>,
pub calldata: Option<Vec<String>>,

#[clap(flatten)]
pub fee_args: FeeArgs,
Expand Down
33 changes: 28 additions & 5 deletions crates/sncast/tests/e2e/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,32 @@ fn test_happy_case() {
"});
}

#[test]
fn test_happy_case_cairo_expression_calldata() {
let args = vec![
"--accounts-file",
ACCOUNT_FILE_PATH,
"call",
"--url",
URL,
"--contract-address",
MAP_CONTRACT_ADDRESS_SEPOLIA,
"--function",
"put",
"--calldata",
"(0x0_felt252, 0x2137)",
"--block-id",
"latest",
];

let snapbox = runner(&args);

snapbox.assert().success().stdout_eq(indoc! {r"
command: call
response: []
"});
}

#[tokio::test]
async fn test_call_after_storage_changed() {
invoke_contract(
Expand Down Expand Up @@ -78,14 +104,11 @@ async fn test_contract_does_not_exist() {
];

let snapbox = runner(&args);
let output = snapbox.assert().success();
let output = snapbox.assert().failure();

assert_stderr_contains(
output,
indoc! {r"
command: call
error: There is no contract at the specified address
"},
"Error: An error occurred in the called contract[..]Requested contract address[..]is not deployed[..]",
);
}

Expand Down
41 changes: 34 additions & 7 deletions crates/sncast/tests/e2e/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ async fn test_invalid_version_and_token_combination(fee_token: &str, version: &s
format!("Error: {fee_token} fee token is not supported for {version} deployment."),
);
}

#[tokio::test]
async fn test_happy_case_with_constructor() {
let args = vec![
Expand Down Expand Up @@ -283,6 +284,35 @@ async fn test_happy_case_with_constructor() {
assert!(matches!(receipt, Deploy(_)));
}

#[tokio::test]
async fn test_happy_case_with_constructor_cairo_expression_calldata() {
let args = vec![
"--accounts-file",
ACCOUNT_FILE_PATH,
"--account",
"user5",
"--int-format",
"--json",
"deploy",
"--url",
URL,
"--fee-token",
"eth",
"--constructor-calldata",
"(0x420, 0x2137_u256)",
"--class-hash",
CONSTRUCTOR_WITH_PARAMS_CONTRACT_CLASS_HASH_SEPOLIA,
];

let snapbox = runner(&args);
let output = snapbox.assert().success().get_output().stdout.clone();

let hash = get_transaction_hash(&output);
let receipt = get_transaction_receipt(hash).await;

assert!(matches!(receipt, Deploy(_)));
}

#[test]
fn test_wrong_calldata() {
let args = vec![
Expand All @@ -298,7 +328,7 @@ fn test_wrong_calldata() {
"--class-hash",
CONSTRUCTOR_WITH_PARAMS_CONTRACT_CLASS_HASH_SEPOLIA,
"--constructor-calldata",
"0x1 0x1",
"0x1 0x2 0x3 0x4",
];

let snapbox = runner(&args);
Expand All @@ -308,7 +338,7 @@ fn test_wrong_calldata() {
output,
indoc! {r"
command: deploy
error: An error occurred in the called contract[..]Failed to deserialize param #2[..]
error: An error occurred in the called contract[..]('Input too long for arguments')[..]
cptartur marked this conversation as resolved.
Show resolved Hide resolved
"},
);
}
Expand All @@ -330,14 +360,11 @@ async fn test_contract_not_declared() {
];

let snapbox = runner(&args);
let output = snapbox.assert().success();
let output = snapbox.assert().failure();

assert_stderr_contains(
output,
indoc! {r"
command: deploy
error: An error occurred in the called contract[..]Class with hash[..]is not declared[..]
"},
"Error: An error occurred in the called contract[..]Class with hash[..]is not declared[..]",
);
}

Expand Down
51 changes: 42 additions & 9 deletions crates/sncast/tests/e2e/invoke.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::helpers::constants::{
ACCOUNT, ACCOUNT_FILE_PATH, DEVNET_OZ_CLASS_HASH_CAIRO_0, MAP_CONTRACT_ADDRESS_SEPOLIA, URL,
ACCOUNT, ACCOUNT_FILE_PATH, DATA_TRANSFORMER_CONTRACT_ADDRESS_SEPOLIA,
DEVNET_OZ_CLASS_HASH_CAIRO_0, MAP_CONTRACT_ADDRESS_SEPOLIA, URL,
};
use crate::helpers::fixtures::{
create_and_deploy_account, create_and_deploy_oz_account, get_transaction_hash,
Expand Down Expand Up @@ -278,14 +279,11 @@ async fn test_contract_does_not_exist() {
];

let snapbox = runner(&args);
let output = snapbox.assert().success();
let output = snapbox.assert().failure();

assert_stderr_contains(
output,
indoc! {r"
command: invoke
error: An error occurred in the called contract[..]Requested contract address[..]is not deployed[..]
"},
"Error: An error occurred in the called contract[..]Requested contract address[..]is not deployed[..]"
);
}

Expand All @@ -312,9 +310,9 @@ fn test_wrong_function_name() {

assert_stderr_contains(
output,
indoc! {r"
command: invoke
error: An error occurred in the called contract[..]Entry point[..]not found in contract[..]
indoc! {"
command: invoke
error: An error occurred in the called contract[..]Entry point[..]not found in contract[..]
"},
);
}
Expand Down Expand Up @@ -386,3 +384,38 @@ fn test_too_low_max_fee() {
"},
);
}

#[tokio::test]
async fn test_happy_case_cairo_expression_calldata() {
let calldata = r"(NestedStructWithField { a: SimpleStruct { a: 0x24 }, b: 96 },)";

let args = vec![
"--accounts-file",
ACCOUNT_FILE_PATH,
"--account",
"user12",
"--int-format",
"--json",
"invoke",
"--url",
URL,
"--contract-address",
DATA_TRANSFORMER_CONTRACT_ADDRESS_SEPOLIA,
"--function",
"nested_struct_fn",
"--calldata",
calldata,
"--max-fee",
"99999999999999999",
"--fee-token",
"eth",
];

let snapbox = runner(&args);
let output = snapbox.assert().success().get_output().stdout.clone();

let hash = get_transaction_hash(&output);
let receipt = get_transaction_receipt(hash).await;

assert!(matches!(receipt, Invoke(_)));
}
Loading
Loading