diff --git a/starknet-core/src/types/contract/legacy.rs b/starknet-core/src/types/contract/legacy.rs index 83294a82..eaf842f5 100644 --- a/starknet-core/src/types/contract/legacy.rs +++ b/starknet-core/src/types/contract/legacy.rs @@ -29,7 +29,8 @@ const API_VERSION: Felt = Felt::ZERO; #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyContractClass { - pub abi: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub abi: Option>, pub entry_points_by_type: RawLegacyEntryPoints, pub program: LegacyProgram, } @@ -459,7 +460,7 @@ impl LegacyContractClass { } let serialized = to_string_pythonic(&ContractArtifactForHash { - abi: &self.abi, + abi: &self.abi.clone().unwrap_or_default(), program: &self.program, }) .map_err(|err| { @@ -476,13 +477,10 @@ impl LegacyContractClass { Ok(CompressedLegacyContractClass { program: self.program.compress()?, entry_points_by_type: self.entry_points_by_type.clone().into(), - abi: Some( - self.abi - .clone() - .into_iter() - .map(|item| item.into()) - .collect(), - ), + abi: match &self.abi { + Some(abi) => Some(abi.clone().into_iter().map(|item| item.into()).collect()), + None => None, + }, }) } } diff --git a/starknet-core/src/types/receipt_block.rs b/starknet-core/src/types/receipt_block.rs index 4287e2e6..5c73d03d 100644 --- a/starknet-core/src/types/receipt_block.rs +++ b/starknet-core/src/types/receipt_block.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use serde_with::serde_as; +use crate::serde::unsigned_field_element::UfeHex; use starknet_types_core::felt::Felt; /// A more idiomatic way to access `execution_status` and `revert_reason`. @@ -48,21 +49,22 @@ impl ReceiptBlock { } } +#[serde_as] +#[derive(Serialize, Deserialize)] +#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] +struct Raw { + #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(as = "Option")] + block_hash: Option, + #[serde(skip_serializing_if = "Option::is_none")] + block_number: Option, +} + impl Serialize for ReceiptBlock { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - #[derive(Serialize)] - #[serde_as] - struct Raw<'a> { - #[serde_as(as = "Option")] - #[serde(skip_serializing_if = "Option::is_none")] - block_hash: Option<&'a Felt>, - #[serde(skip_serializing_if = "Option::is_none")] - block_number: Option<&'a u64>, - } - let raw = match self { Self::Pending => Raw { block_hash: None, @@ -72,8 +74,8 @@ impl Serialize for ReceiptBlock { block_hash, block_number, } => Raw { - block_hash: Some(block_hash), - block_number: Some(block_number), + block_hash: Some(*block_hash), + block_number: Some(*block_number), }, }; @@ -86,17 +88,6 @@ impl<'de> Deserialize<'de> for ReceiptBlock { where D: serde::Deserializer<'de>, { - #[derive(Deserialize)] - #[serde_as] - #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] - struct Raw { - #[serde_as(as = "Option")] - #[serde(skip_serializing_if = "Option::is_none")] - block_hash: Option, - #[serde(skip_serializing_if = "Option::is_none")] - block_number: Option, - } - let raw = Raw::deserialize(deserializer)?; match (raw.block_hash, raw.block_number) { diff --git a/starknet-core/test-data/raw_gateway_responses/get_block/16_does_not_exist.txt b/starknet-core/test-data/raw_gateway_responses/get_block/16_does_not_exist.txt new file mode 100644 index 00000000..b01042b4 --- /dev/null +++ b/starknet-core/test-data/raw_gateway_responses/get_block/16_does_not_exist.txt @@ -0,0 +1 @@ +{"code": "StarknetErrorCode.BLOCK_NOT_FOUND", "message": "Block hash 0x0 does not exist."} diff --git a/starknet-providers/Cargo.toml b/starknet-providers/Cargo.toml index bfcf2275..273de87d 100644 --- a/starknet-providers/Cargo.toml +++ b/starknet-providers/Cargo.toml @@ -24,7 +24,7 @@ url = "2.3.1" reqwest = { version = "0.11.16", default-features = false, features = ["rustls-tls"] } thiserror = "1.0.40" serde = "1.0.160" -serde_json = "1.0.96" +serde_json = { version = "1.0.96" } serde_with = "2.3.2" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/starknet-providers/src/sequencer/mod.rs b/starknet-providers/src/sequencer/mod.rs index 8e83a11e..2b8b5cf1 100644 --- a/starknet-providers/src/sequencer/mod.rs +++ b/starknet-providers/src/sequencer/mod.rs @@ -190,10 +190,7 @@ impl SequencerGatewayProvider { url } - async fn send_get_request(&self, url: Url) -> Result - where - T: DeserializeOwned, - { + async fn send_get_request_raw(&self, url: Url) -> Result { trace!("Sending GET request to sequencer API ({})", url); let mut request = self.client.get(url); @@ -206,13 +203,21 @@ impl SequencerGatewayProvider { Err(ProviderError::RateLimited) } else { let body = res.text().await.map_err(GatewayClientError::Network)?; - trace!("Response from sequencer API: {}", body); - Ok(serde_json::from_str(&body).map_err(GatewayClientError::Serde)?) + Ok(body) } } + async fn send_get_request(&self, url: Url) -> Result + where + T: DeserializeOwned, + { + let body = self.send_get_request_raw(url).await?; + + Ok(serde_json::from_str(&body).map_err(GatewayClientError::Serde)?) + } + async fn send_post_request(&self, url: Url, body: &Q) -> Result where Q: Serialize, @@ -316,6 +321,24 @@ impl SequencerGatewayProvider { .into() } + #[deprecated( + note = "Sequencer-specific functions are deprecated. Use it via the Provider trait instead." + )] + pub async fn get_state_update_with_block( + &self, + block_identifier: BlockId, + ) -> Result { + let mut request_url = self.extend_feeder_gateway_url("get_state_update"); + append_block_id(&mut request_url, block_identifier); + request_url + .query_pairs_mut() + .append_pair("includeBlock", "true"); + + self.send_get_request::>(request_url) + .await? + .into() + } + #[deprecated( note = "Sequencer-specific functions are deprecated. Use it via the Provider trait instead." )] @@ -349,9 +372,23 @@ impl SequencerGatewayProvider { .append_pair("classHash", &format!("{class_hash:#x}")); append_block_id(&mut request_url, block_identifier); - self.send_get_request::>(request_url) - .await? - .into() + // `body` can be a `FlattenedSierraClass`, a `LegacyContractClass` or a `SequenceError`. + // All are "untagged", meaning we have to try them out sequencialy to find out which it is. + // Due to `serde` limitations, we cannot express this inside a `Deserialize` impl while using `raw_value`, + // so we don't follow the regular deserialization flow, and do it outside of the trait. + let body = self.send_get_request_raw(request_url).await?.into_bytes(); + + if let Ok(value) = DeployedClass::deserialize_from_json_deployed_class(&body) { + return Ok(value); + } + if let Ok(value) = serde_json::from_slice::(&body) { + return Err(value.into()); + } + + Err(GatewayClientError::Serde(serde::de::Error::custom( + "data did not match any variant of enum GatewayResponse", + )) + .into()) } #[deprecated( @@ -571,33 +608,46 @@ mod tests { ] .into_iter() { - serde_json::from_str::>(raw).unwrap(); + DeployedClass::deserialize_from_json_deployed_class(raw.as_bytes()).unwrap(); } } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - fn test_get_class_by_hash_deser_not_declared() { - match serde_json::from_str::>(include_str!( - "../../test-data/raw_gateway_responses/get_class_by_hash/2_not_declared.txt" + fn test_get_block_deser_does_not_exist() { + match serde_json::from_str::>(include_str!( + "../../test-data/raw_gateway_responses/get_block/16_does_not_exist.txt" )) .unwrap() { GatewayResponse::SequencerError(err) => { - assert_eq!(err.code, ErrorCode::UndeclaredClass); + assert_eq!(err.code, ErrorCode::BlockNotFound); } _ => panic!("Unexpected result"), } } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - fn test_error_deser_invalid_contract_class() { - let error: SequencerError = serde_json::from_str(include_str!( - "../../test-data/serde/sequencer_error_invalid_contract_class.json" - )) - .unwrap(); + #[tokio::test] + #[allow(deprecated)] + async fn big_felt_are_deserialized_using_raw_data() { + let provider = SequencerGatewayProvider::starknet_alpha_mainnet(); + + let class_hash = Felt::from_hex_unchecked( + "0x50ca5f07e12e74a7f0c4dea9cdd26b34a29780be00d1430a6d511f933ec9ec6", + ); + + let contract_class = provider + .get_class_by_hash(class_hash, BlockId::Latest) + .await + .unwrap(); + + let computed_class_hash = match contract_class { + DeployedClass::SierraClass(_) => panic!("this should be a legacy contract"), + DeployedClass::LegacyClass(cc) => cc.class_hash().unwrap(), + }; - assert_eq!(error.code, ErrorCode::InvalidContractClass); + // This contract contains big fields, if it is deserialized without using `raw_values`, + // those will be treated as float and the values will change, therefore chainging the class hash + assert_eq!(class_hash, computed_class_hash); } } diff --git a/starknet-providers/src/sequencer/models/contract.rs b/starknet-providers/src/sequencer/models/contract.rs index 04584d51..775b5435 100644 --- a/starknet-providers/src/sequencer/models/contract.rs +++ b/starknet-providers/src/sequencer/models/contract.rs @@ -1,7 +1,7 @@ -use std::io::Write; +use std::{fmt::Formatter, io::Write}; use flate2::{write::GzEncoder, Compression}; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{de::Error, Deserialize, Deserializer, Serialize}; use serde_with::serde_as; use starknet_core::{ serde::{byte_array::base64::serialize as base64_ser, unsigned_field_element::UfeHex}, @@ -52,23 +52,60 @@ pub enum DecompressProgramError { Io(std::io::Error), } -// We need to manually implement this because `raw_value` doesn't work with `untagged`: -// https://github.com/serde-rs/serde/issues/1183 -impl<'de> Deserialize<'de> for DeployedClass { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let temp_value = serde_json::Value::deserialize(deserializer)?; - if let Ok(value) = FlattenedSierraClass::deserialize(&temp_value) { - return Ok(Self::SierraClass(value)); +impl DeployedClass { + // We need to manually implement this because `raw_value` doesn't work with `untagged`: + // https://github.com/serde-rs/serde/issues/1183 + // Preventing us to impl `Deserialize` on DeployedClass in a satisfying way. + pub fn deserialize_from_json_deployed_class(json: &[u8]) -> Result { + // Those are the fields of both `FlattenedSierraClass` and `LegacyClass` mixed together. + // We don't deserialize their content, we just check whether or not those fields are present. + #[derive(Deserialize)] + struct BulkDeployedClassFields<'a> { + // Common fields + #[serde(borrow)] + pub entry_points_by_type: &'a serde_json::value::RawValue, + #[serde(borrow)] + pub abi: &'a serde_json::value::RawValue, + // Sierra contract specific fields + #[serde(borrow)] + #[serde(skip_serializing_if = "Option::is_none")] + pub sierra_program: Option<&'a serde_json::value::RawValue>, + #[serde(borrow)] + #[serde(skip_serializing_if = "Option::is_none")] + pub contract_class_version: Option<&'a serde_json::value::RawValue>, + // Cairo countract specific field + #[serde(borrow)] + #[serde(skip_serializing_if = "Option::is_none")] + pub program: Option<&'a serde_json::value::RawValue>, } - if let Ok(value) = LegacyContractClass::deserialize(&temp_value) { - return Ok(Self::LegacyClass(value)); - } - Err(serde::de::Error::custom( - "data did not match any variant of enum DeployedClass", - )) + + let buld_fields: BulkDeployedClassFields = serde_json::from_slice(json).unwrap(); + + let deployed_class = match buld_fields.program { + Some(program) => DeployedClass::LegacyClass(LegacyContractClass { + abi: serde_json::from_str(buld_fields.abi.get())?, + entry_points_by_type: serde_json::from_str(buld_fields.entry_points_by_type.get())?, + program: serde_json::from_str(program.get())?, + }), + None => DeployedClass::SierraClass(FlattenedSierraClass { + sierra_program: serde_json::from_str( + buld_fields + .sierra_program + .ok_or(serde_json::Error::missing_field("sierra_program"))? + .get(), + )?, + contract_class_version: serde_json::from_str( + buld_fields + .contract_class_version + .ok_or(serde_json::Error::missing_field("contract_class_version"))? + .get(), + )?, + entry_points_by_type: serde_json::from_str(buld_fields.entry_points_by_type.get())?, + abi: serde_json::from_str(buld_fields.abi.get())?, + }), + }; + + Ok(deployed_class) } } diff --git a/starknet-providers/src/sequencer/models/mod.rs b/starknet-providers/src/sequencer/models/mod.rs index 3e269958..467e61f1 100644 --- a/starknet-providers/src/sequencer/models/mod.rs +++ b/starknet-providers/src/sequencer/models/mod.rs @@ -12,9 +12,9 @@ pub use block::{Block, BlockId, BlockStatus}; mod transaction; pub use transaction::{ - DeclareTransaction, DeployAccountTransaction, DeployTransaction, EntryPointType, - InvokeFunctionTransaction, L1HandlerTransaction, TransactionFailureReason, TransactionInfo, - TransactionStatusInfo, TransactionType, + DataAvailabilityMode, DeclareTransaction, DeployAccountTransaction, DeployTransaction, + EntryPointType, InvokeFunctionTransaction, L1HandlerTransaction, ResourceBoundsMapping, + TransactionFailureReason, TransactionInfo, TransactionStatusInfo, TransactionType, }; mod transaction_receipt; @@ -47,6 +47,7 @@ pub use contract::{CompressedLegacyContractClass, DeployedClass}; pub mod state_update; pub use state_update::StateUpdate; +pub use state_update::StateUpdateWithBlock; pub mod trace; pub use trace::{BlockTraces, TransactionTrace}; diff --git a/starknet-providers/src/sequencer/models/state_update.rs b/starknet-providers/src/sequencer/models/state_update.rs index 0447b96c..596eedd0 100644 --- a/starknet-providers/src/sequencer/models/state_update.rs +++ b/starknet-providers/src/sequencer/models/state_update.rs @@ -3,6 +3,8 @@ use serde_with::serde_as; use starknet_core::{serde::unsigned_field_element::UfeHex, types::Felt}; use std::collections::HashMap; +use super::Block; + #[serde_as] #[derive(Debug, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] @@ -16,6 +18,14 @@ pub struct StateUpdate { pub state_diff: StateDiff, } +#[serde_as] +#[derive(Debug, Deserialize)] +#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] +pub struct StateUpdateWithBlock { + pub state_update: StateUpdate, + pub block: Block, +} + #[serde_as] #[derive(Debug, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] diff --git a/starknet-providers/src/sequencer/models/transaction_receipt.rs b/starknet-providers/src/sequencer/models/transaction_receipt.rs index fdcaf221..a00a30fd 100644 --- a/starknet-providers/src/sequencer/models/transaction_receipt.rs +++ b/starknet-providers/src/sequencer/models/transaction_receipt.rs @@ -76,6 +76,7 @@ pub struct ExecutionResources { pub n_memory_holes: u64, pub builtin_instance_counter: BuiltinInstanceCounter, pub data_availability: Option, + pub total_gas_consumed: Option } #[derive(Debug, Deserialize)]