Skip to content
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
20 changes: 10 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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 }
2 changes: 2 additions & 0 deletions packages/fuels-code-gen/src/program_bindings/abigen/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
}

Expand All @@ -27,6 +28,7 @@ fn resolve_logs(logged_types: &[FullLoggedType]) -> Vec<ResolvedLog> {
let resolved_type = TypeResolver::default()
.resolve(&l.application)
.expect("Failed to resolve log type");
println!("{:#?}", resolved_type);

ResolvedLog {
log_id: l.log_id.clone(),
Expand Down
63 changes: 62 additions & 1 deletion packages/fuels-core/src/codec/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
Expand Down Expand Up @@ -110,6 +115,22 @@ impl LogDecoder {
LogResult { results }
}

/// Get all filtered logs results from the given receipts as `Result<String>`
pub fn filter_logs_at_offset(
&self,
receipts: &[Receipt],
token: Token,
offset: u16,
) -> Result<LogResult> {
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<String> {
self.log_formatters
.get(log_id)
Expand Down Expand Up @@ -192,6 +213,11 @@ trait ExtractLogIdData {
fn extract_log_id_and_data(self) -> Self::Output;
}

trait ExtractFilterLogIdData {
type Output: Iterator<Item = (LogId, Vec<u8>)>;
fn extract_filtered_log_id_and_data(self, token: Token, offset: u16) -> Result<Self::Output>;
}

impl<'a, I: Iterator<Item = &'a Receipt>> ExtractLogIdData for I {
type Output = FilterMap<Self, fn(&Receipt) -> Option<(LogId, Vec<u8>)>>;
fn extract_log_id_and_data(self) -> Self::Output {
Expand All @@ -210,6 +236,41 @@ impl<'a, I: Iterator<Item = &'a Receipt>> ExtractLogIdData for I {
}
}

impl<'a, I: Iterator<Item = &'a Receipt>> ExtractFilterLogIdData for I {
type Output = std::vec::IntoIter<(LogId, Vec<u8>)>;
fn extract_filtered_log_id_and_data(self, token: Token, offset: u16) -> Result<Self::Output> {
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<u8>)> = 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,
Expand Down
54 changes: 54 additions & 0 deletions packages/fuels-core/src/types/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
// }
}
7 changes: 5 additions & 2 deletions packages/fuels-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
11 changes: 10 additions & 1 deletion packages/fuels-programs/src/responses/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -25,6 +25,15 @@ impl<D> CallResponse<D> {
self.log_decoder.decode_logs(&self.tx_status.receipts)
}

pub fn filter_logs<O>(&self, token: Token, offset: O) -> Result<LogResult>
where
O: Into<u16>,
{
let offset = offset.into();
self.log_decoder
.filter_logs_at_offset(&self.tx_status.receipts, token, offset)
}

pub fn decode_logs_with_type<T: Tokenizable + Parameterize + 'static>(&self) -> Result<Vec<T>> {
self.log_decoder
.decode_logs_with_type::<T>(&self.tx_status.receipts)
Expand Down