diff --git a/Cargo.toml b/Cargo.toml index bf851669db..b670b9cfbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ readme = "README.md" license = "Apache-2.0" repository = "https://github.com/FuelLabs/fuels-rs" rust-version = "1.85.0" -version = "0.72.0" +version = "0.73.0" [workspace.dependencies] Inflector = "0.11.4" @@ -53,7 +53,7 @@ cynic = { version = "3.1.0", default-features = false } test-case = { version = "3.3", default-features = false } eth-keystore = "0.5.0" flate2 = { version = "1.0", default-features = false } -fuel-abi-types = "0.8.0" +fuel-abi-types = { path = "../sway/fuel-abi-types/", version = "0.13.0" } futures = "0.3.29" hex = { version = "0.4.3", default-features = false } itertools = "0.12.0" @@ -114,11 +114,11 @@ fuel-types = { version = "0.60.2" } fuel-vm = { version = "0.60.2" } # Workspace projects -fuels = { version = "0.72.0", path = "./packages/fuels", default-features = false } -fuels-accounts = { version = "0.72.0", path = "./packages/fuels-accounts", default-features = false } -fuels-code-gen = { version = "0.72.0", path = "./packages/fuels-code-gen", default-features = false } -fuels-core = { version = "0.72.0", path = "./packages/fuels-core", default-features = false } -fuels-macros = { version = "0.72.0", path = "./packages/fuels-macros", default-features = false } -fuels-programs = { version = "0.72.0", path = "./packages/fuels-programs", default-features = false } -fuels-test-helpers = { version = "0.72.0", path = "./packages/fuels-test-helpers", default-features = false } -versions-replacer = { version = "0.72.0", path = "./scripts/versions-replacer", default-features = false } +fuels = { version = "0.73.0", path = "./packages/fuels", default-features = false } +fuels-accounts = { version = "0.73.0", path = "./packages/fuels-accounts", default-features = false } +fuels-code-gen = { version = "0.73.0", path = "./packages/fuels-code-gen", default-features = false } +fuels-core = { version = "0.73.0", path = "./packages/fuels-core", default-features = false } +fuels-macros = { version = "0.73.0", path = "./packages/fuels-macros", default-features = false } +fuels-programs = { version = "0.73.0", path = "./packages/fuels-programs", default-features = false } +fuels-test-helpers = { version = "0.73.0", path = "./packages/fuels-test-helpers", default-features = false } +versions-replacer = { version = "0.73.0", path = "./scripts/versions-replacer", default-features = false } diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/logs.rs b/packages/fuels-code-gen/src/program_bindings/abigen/logs.rs index 744134b789..6d533a4b4d 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/logs.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/logs.rs @@ -10,6 +10,7 @@ pub(crate) fn log_formatters_instantiation_code( ) -> TokenStream { let resolved_logs = resolve_logs(logged_types); let log_id_log_formatter_pairs = generate_log_id_log_formatter_pairs(&resolved_logs); + println!("{:#?}", log_id_log_formatter_pairs); quote! {::fuels::core::codec::log_formatters_lookup(vec![#(#log_id_log_formatter_pairs),*], #contract_id)} } @@ -27,6 +28,7 @@ fn resolve_logs(logged_types: &[FullLoggedType]) -> Vec { let resolved_type = TypeResolver::default() .resolve(&l.application) .expect("Failed to resolve log type"); + println!("{:#?}", resolved_type); ResolvedLog { log_id: l.log_id.clone(), diff --git a/packages/fuels-core/src/codec/logs.rs b/packages/fuels-core/src/codec/logs.rs index c8c533f07a..c657194b95 100644 --- a/packages/fuels-core/src/codec/logs.rs +++ b/packages/fuels-core/src/codec/logs.rs @@ -10,9 +10,14 @@ use fuel_tx::{ContractId, Receipt}; use crate::{ codec::{ABIDecoder, DecoderConfig}, traits::{Parameterize, Tokenizable}, - types::errors::{Error, Result, error}, + types::{ + Token, + errors::{Error, Result, error}, + }, }; +use super::{ABIEncoder, EncoderConfig}; + #[derive(Clone)] pub struct LogFormatter { formatter: fn(DecoderConfig, &[u8]) -> Result, @@ -110,6 +115,22 @@ impl LogDecoder { LogResult { results } } + /// Get all filtered logs results from the given receipts as `Result` + pub fn filter_logs_at_offset( + &self, + receipts: &[Receipt], + token: Token, + offset: u16, + ) -> Result { + let results = receipts + .iter() + .extract_filtered_log_id_and_data(token, offset)? + .map(|(log_id, data)| self.format_log(&log_id, &data)) + .collect(); + + Ok(LogResult { results }) + } + fn format_log(&self, log_id: &LogId, data: &[u8]) -> Result { self.log_formatters .get(log_id) @@ -192,6 +213,11 @@ trait ExtractLogIdData { fn extract_log_id_and_data(self) -> Self::Output; } +trait ExtractFilterLogIdData { + type Output: Iterator)>; + fn extract_filtered_log_id_and_data(self, token: Token, offset: u16) -> Result; +} + impl<'a, I: Iterator> ExtractLogIdData for I { type Output = FilterMap Option<(LogId, Vec)>>; fn extract_log_id_and_data(self) -> Self::Output { @@ -210,6 +236,41 @@ impl<'a, I: Iterator> ExtractLogIdData for I { } } +impl<'a, I: Iterator> ExtractFilterLogIdData for I { + type Output = std::vec::IntoIter<(LogId, Vec)>; + fn extract_filtered_log_id_and_data(self, token: Token, offset: u16) -> Result { + if !token.is_exact_size_abi() { + return Err(Error::Other("Token is not a fixed-size ABI type".into())); + } + + let encoder = ABIEncoder::new(EncoderConfig::default()); + let encoded_token = encoder.encode(&[token])?; + + let offset_usize = offset as usize; + let tok_len = encoded_token.len(); + + let results: Vec<(LogId, Vec)> = self + .filter_map(|r| { + if let Receipt::LogData { rb, data: Some(data), id, .. } = r { + // must have enough bytes after offset + if data.len() >= offset_usize + tok_len + // and the slice matches our encoded_token + && &data[offset_usize .. offset_usize + tok_len] == encoded_token.as_slice() + { + Some((LogId(*id, rb.to_string()), data.clone())) + } else { + None + } + } else { + None + } + }) + .collect(); + + Ok(results.into_iter()) + } +} + pub fn log_formatters_lookup( log_id_log_formatter_pairs: Vec<(String, LogFormatter)>, contract_id: ContractId, diff --git a/packages/fuels-core/src/types/token.rs b/packages/fuels-core/src/types/token.rs index 2e4353f784..8ccd558bb5 100644 --- a/packages/fuels-core/src/types/token.rs +++ b/packages/fuels-core/src/types/token.rs @@ -88,3 +88,57 @@ impl Default for Token { Token::U8(0) } } + +impl Token { + /// Returns true if this [Token] is an exact-size ABI type. + pub fn is_exact_size_abi(&self) -> bool { + match self { + Token::Unit + | Token::Bool(_) + | Token::U8(_) | Token::U16(_) | Token::U32(_) | Token::U64(_) | Token::U128(_) | Token::U256(_) + | Token::B256(_) => true, + + // Dynamic or heap-allocated + Token::Bytes(_) | Token::String(_) | Token::RawSlice(_) + | Token::StringArray(_) | Token::StringSlice(_) + | Token::Vector(_) => false, + + // Nested container: all elements must be exact-size + Token::Tuple(elems) | Token::Array(elems) | Token::Struct(elems) => { + elems.iter().all(|t| t.is_exact_size_abi()) + } + + // Enum: second element of selector is the payload Token + Token::Enum(selector) => selector.1.is_exact_size_abi(), + } + } +} + +mod tests { + + + #[test] + fn primitives() { + assert!(Token::U32(0).is_exact_size_abi()); + assert!(Token::B256([0u8; 32]).is_exact_size_abi()); + assert!(!Token::String("a".into()).is_exact_size_abi()); + } + + #[test] + fn nested() { + let good = Token::Tuple(vec![Token::U16(1), Token::Bool(true)]); + assert!(good.is_exact_size_abi()); + + let bad = Token::Struct(vec![Token::U8(2), Token::Bytes(vec![1])]); + assert!(!bad.is_exact_size_abi()); + } + + // #[test] + // fn enum_token() { + // let ok = Token::Enum(Box::new((0, Token::U64(5), EnumVariants::default()))); + // assert!(ok.is_exact_size_abi()); + + // let err = Token::Enum(Box::new((1, Token::String("e".into()), EnumVariants::default()))); + // assert!(!err.is_exact_size_abi()); + // } +} diff --git a/packages/fuels-macros/src/lib.rs b/packages/fuels-macros/src/lib.rs index 7804de7699..9a036e51cd 100644 --- a/packages/fuels-macros/src/lib.rs +++ b/packages/fuels-macros/src/lib.rs @@ -36,9 +36,12 @@ mod setup_program_test; pub fn abigen(input: TokenStream) -> TokenStream { let targets = parse_macro_input!(input as MacroAbigenTargets); - Abigen::generate(targets.into(), false) + let code = Abigen::generate(targets.into(), false) .expect("abigen generation failed") - .into() + .into(); + println!("{}", code); + + code } #[proc_macro] diff --git a/packages/fuels-programs/src/responses/call.rs b/packages/fuels-programs/src/responses/call.rs index f8041e7559..89807c51ca 100644 --- a/packages/fuels-programs/src/responses/call.rs +++ b/packages/fuels-programs/src/responses/call.rs @@ -4,7 +4,7 @@ use fuel_tx::TxId; use fuels_core::{ codec::{LogDecoder, LogResult}, traits::{Parameterize, Tokenizable}, - types::{errors::Result, tx_status::Success}, + types::{Token, errors::Result, tx_status::Success}, }; /// [`CallResponse`] is a struct that is returned by a call to the contract or script. Its value @@ -25,6 +25,15 @@ impl CallResponse { self.log_decoder.decode_logs(&self.tx_status.receipts) } + pub fn filter_logs(&self, token: Token, offset: O) -> Result + where + O: Into, + { + let offset = offset.into(); + self.log_decoder + .filter_logs_at_offset(&self.tx_status.receipts, token, offset) + } + pub fn decode_logs_with_type(&self) -> Result> { self.log_decoder .decode_logs_with_type::(&self.tx_status.receipts)