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

Franciszekjob/2604 cast command output improvement #2769

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
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
24 changes: 23 additions & 1 deletion crates/conversions/src/byte_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,27 @@ use crate::{serde::serialize::SerializeToFeltVec, string::TryFromHexStr};
use cairo_lang_runner::short_string::as_cairo_short_string_ex;
use cairo_lang_utils::byte_array::{BYTES_IN_WORD, BYTE_ARRAY_MAGIC};
use cairo_serde_macros::{CairoDeserialize, CairoSerialize};
use serde::{Deserialize, Serialize};
use starknet_types_core::felt::Felt;
use std::fmt;

#[derive(CairoDeserialize, CairoSerialize, Clone, Debug, PartialEq)]
#[derive(Serialize, Deserialize, CairoDeserialize, CairoSerialize, Clone, Debug, PartialEq)]
pub struct ByteArray {
words: Vec<Felt>,
pending_word: Felt,
pending_word_len: usize,
}

impl Default for ByteArray {
fn default() -> Self {
ByteArray {
words: Vec::new(),
pending_word: Felt::ZERO,
pending_word_len: 0,
}
}
}

impl From<&str> for ByteArray {
fn from(value: &str) -> Self {
let chunks = value.as_bytes().chunks_exact(BYTES_IN_WORD);
Expand All @@ -31,6 +42,17 @@ impl From<&str> for ByteArray {
}
}

impl ByteArray {
#[must_use]
pub fn new(words: Vec<Felt>, pending_word: Felt, pending_word_len: usize) -> Self {
Self {
words,
pending_word,
pending_word_len,
}
}
}

impl ByteArray {
#[must_use]
pub fn serialize_with_magic(&self) -> Vec<Felt> {
Expand Down
2 changes: 1 addition & 1 deletion crates/conversions/src/padded_felt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl Serialize for PaddedFelt {

impl LowerHex for PaddedFelt {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
fmt::LowerHex::fmt(&self.0, f)
write!(f, "{:#064x}", self.0)
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/sncast/src/helpers/block_explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ mod tests {
};

async fn assert_valid_links(input: &str) {
let pattern = Regex::new(r"transaction: |contract: |class: ").unwrap();
let pattern = Regex::new(r"Transaction: |Contract: |Class: ").unwrap();
let links = pattern.replace_all(input, "");
let mut links = links.split('\n');

Expand Down
62 changes: 53 additions & 9 deletions crates/sncast/src/response/print.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use super::structs::CommandResponse;
use crate::NumbersFormat;
use anyhow::Result;
use conversions::byte_array::ByteArray;
use itertools::Itertools;
use serde::{Serialize, Serializer};
use serde_json::Value;
use starknet_types_core::felt::Felt;
use std::{collections::HashMap, fmt::Display, str::FromStr};
use std::{
collections::{HashMap, HashSet},
fmt::Display,
str::FromStr,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
Expand Down Expand Up @@ -66,6 +71,40 @@ impl Display for OutputValue {
}
}

fn byte_array_object_to_string(value: Value) -> OutputValue {
let byte_array_keys: HashSet<String> = ["pending_word", "pending_word_len", "words"]
.iter()
.map(|&s| s.to_string())
.collect();

if let Value::Object(obj) = value {
let obj_keys: HashSet<_> = obj.keys().cloned().collect();

if obj_keys == byte_array_keys {
let words: Vec<Felt> = obj
.get("words")
.unwrap()
.as_array()
.unwrap()
.iter()
.map(|v| Felt::from_str(v.as_str().unwrap()).unwrap())
.collect();
let pending_word =
Felt::from_str(obj.get("pending_word").unwrap().as_str().unwrap()).unwrap();
let pending_word_len =
usize::try_from(obj.get("pending_word_len").unwrap().as_u64().unwrap()).unwrap();
let byte_array = ByteArray::new(words, pending_word, pending_word_len);
OutputValue::String(byte_array.to_string())
} else {
panic!(
"Object keys do not match expected byte array keys. Can only serialize byte arrays"
);
}
} else {
panic!("Expected an object");
}
}

impl From<Value> for OutputValue {
fn from(value: Value) -> Self {
match value {
Expand All @@ -76,6 +115,7 @@ impl From<Value> for OutputValue {
),
Value::String(s) => OutputValue::String(s.to_string()),
Value::Bool(b) => OutputValue::String(b.to_string()),
Value::Object(obj) => byte_array_object_to_string(Value::Object(obj)),
s => panic!("{s:?} cannot be auto-serialized to output"),
}
}
Expand Down Expand Up @@ -164,20 +204,24 @@ impl OutputData {
serde_json::to_string(&mapping).map_err(anyhow::Error::from)
}

fn to_lines(&self, command: &str) -> String {
let fields = self
.0
.iter()
.map(|(key, val)| format!("{key}: {val}"))
.join("\n");
fn to_lines(&self) -> String {
if let Some((_, message)) = self.0.iter().find(|(key, _)| key == "message") {
format!("{message}")
} else {
let fields = self
.0
.iter()
.map(|(key, val)| format!("{key}: {val}"))
.join("\n");

format!("command: {command}\n{fields}")
fields.to_string()
}
}

fn to_string_pretty(&self, command: &str, output_format: OutputFormat) -> Result<String> {
match output_format {
OutputFormat::Json => self.to_json(command),
OutputFormat::Human => Ok(self.to_lines(command)),
OutputFormat::Human => Ok(self.to_lines()),
}
}
}
Expand Down
16 changes: 9 additions & 7 deletions crates/sncast/src/response/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use super::explorer_link::OutputLink;
use crate::helpers::block_explorer;
use crate::helpers::block_explorer::LinkProvider;
use camino::Utf8PathBuf;
use conversions::padded_felt::PaddedFelt;
use conversions::serde::serialize::CairoSerialize;
use conversions::{byte_array::ByteArray, padded_felt::PaddedFelt};
use indoc::formatdoc;
use serde::{Deserialize, Serialize, Serializer};
use starknet_types_core::felt::Felt;
Expand Down Expand Up @@ -51,6 +51,8 @@ impl CommandResponse for DeployResponse {}
pub struct DeclareTransactionResponse {
pub class_hash: PaddedFelt,
pub transaction_hash: PaddedFelt,
#[serde(skip)]
pub message: ByteArray,
}

impl CommandResponse for DeclareTransactionResponse {}
Expand Down Expand Up @@ -169,7 +171,7 @@ impl OutputLink for InvokeResponse {

fn format_links(&self, provider: Box<dyn LinkProvider>) -> String {
format!(
"transaction: {}",
"Transaction: {}",
provider.transaction(self.transaction_hash)
)
}
Expand All @@ -181,8 +183,8 @@ impl OutputLink for DeployResponse {
fn format_links(&self, provider: Box<dyn LinkProvider>) -> String {
formatdoc!(
"
contract: {}
transaction: {}
Contract: {}
Transaction: {}
",
provider.contract(self.contract_address),
provider.transaction(self.transaction_hash)
Expand All @@ -196,8 +198,8 @@ impl OutputLink for DeclareTransactionResponse {
fn format_links(&self, provider: Box<dyn LinkProvider>) -> String {
formatdoc!(
"
class: {}
transaction: {}
Class: {}
Transaction: {}
",
provider.class(self.class_hash),
provider.transaction(self.transaction_hash)
Expand All @@ -209,6 +211,6 @@ impl OutputLink for AccountCreateResponse {
const TITLE: &'static str = "account creation";

fn format_links(&self, provider: Box<dyn LinkProvider>) -> String {
format!("account: {}", provider.contract(self.address))
format!("Account: {}", provider.contract(self.address))
}
}
43 changes: 34 additions & 9 deletions crates/sncast/src/starknet_commands/declare.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::{anyhow, Context, Result};
use clap::{Args, ValueEnum};
use conversions::byte_array::ByteArray;
use conversions::padded_felt::PaddedFelt;
use conversions::IntoConv;
use scarb_api::StarknetContractArtifacts;
use sncast::helpers::error::token_not_supported_for_declaration;
Expand Down Expand Up @@ -131,15 +132,21 @@ pub async fn declare(
Ok(DeclareTransactionResult {
transaction_hash,
class_hash,
}) => handle_wait_for_tx(
account.provider(),
transaction_hash,
DeclareResponse::Success(DeclareTransactionResponse {
class_hash: class_hash.into_(),
transaction_hash: transaction_hash.into_(),
}),
wait_config,
)
}) => {
let message = ByteArray::from(
generate_message(class_hash, transaction_hash, declare.rpc.url).as_str(),
);
handle_wait_for_tx(
account.provider(),
transaction_hash,
DeclareResponse::Success(DeclareTransactionResponse {
class_hash: class_hash.into_(),
transaction_hash: transaction_hash.into_(),
message,
}),
wait_config,
)
}
.await
.map_err(StarknetCommandError::from),
Err(Provider(ProviderError::StarknetError(StarknetError::ClassAlreadyDeclared)))
Expand All @@ -153,3 +160,21 @@ pub async fn declare(
Err(error) => Err(anyhow!(format!("Unexpected error occurred: {error}")).into()),
}
}

fn generate_message(class_hash: Felt, transaction_hash: Felt, rpc_url: Option<String>) -> String {
let padded_transaction_hash: PaddedFelt = transaction_hash.into_();
let padded_class_hash: PaddedFelt = class_hash.into_();
let rpc_url = if let Some(url) = rpc_url {
format!(" --url {url}")
} else {
String::new()
};

format!(
"Contract successfully declared!\n\n\
Class hash: {padded_transaction_hash:#x}\n\
Transaction hash: {padded_transaction_hash:#x}\n\n\
In order to deploy it, run:\n\
sncast deploy --class-hash {padded_class_hash:#x} --fee-token strk{rpc_url}",
)
}
7 changes: 7 additions & 0 deletions crates/sncast/src/state/state_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ mod tests {
use crate::response::structs::DeclareTransactionResponse;
use crate::state::state_file::ScriptTransactionOutput::ErrorResponse;
use camino::Utf8PathBuf;
use conversions::byte_array::ByteArray;
use conversions::string::TryFromHexStr;
use conversions::IntoConv;
use starknet_types_core::felt::Felt;
Expand Down Expand Up @@ -422,6 +423,8 @@ mod tests {
DeclareTransactionResponse {
class_hash: Felt::try_from_hex_str("0x123").unwrap().into_(),
transaction_hash: Felt::try_from_hex_str("0x321").unwrap().into_(),
// TODO: Update message
message: ByteArray::from("sdsd"),
},
)),
status: ScriptTransactionStatus::Success,
Expand Down Expand Up @@ -461,6 +464,8 @@ mod tests {
DeclareTransactionResponse {
class_hash: Felt::try_from_hex_str("0x1").unwrap().into_(),
transaction_hash: Felt::try_from_hex_str("0x2").unwrap().into_(),
// TODO: Update message
message: ByteArray::from("sdsd"),
},
)),
status: ScriptTransactionStatus::Success,
Expand Down Expand Up @@ -534,6 +539,8 @@ mod tests {
DeclareTransactionResponse {
class_hash: Felt::try_from_hex_str("0x1").unwrap().into_(),
transaction_hash: Felt::try_from_hex_str("0x2").unwrap().into_(),
// TODO: Update message
message: ByteArray::from("sdsd"),
},
)),
status: ScriptTransactionStatus::Success,
Expand Down
Loading
Loading