Skip to content

Commit

Permalink
Integrated data transformer with sncast commands
Browse files Browse the repository at this point in the history
commit-id:9557f1ee
  • Loading branch information
integraledelebesgue committed Oct 14, 2024
1 parent c693949 commit 769ab4f
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 58 deletions.
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?;

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?;

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')[..]
"},
);
}
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

0 comments on commit 769ab4f

Please sign in to comment.