@@ -169,7 +169,7 @@ pub const EthNamespace = struct {
169169
170170 /// eth_call - Executes a message call (doesn't create a transaction)
171171 pub fn call (self : EthNamespace , params : types.CallParams , block : types.BlockParameter ) ! []u8 {
172- const call_obj = try callParamsToJson (self .client .allocator , params );
172+ var call_obj = try callParamsToJson (self .client .allocator , params );
173173 defer call_obj .deinit ();
174174
175175 const block_param = try blockParameterToString (self .client .allocator , block );
@@ -193,7 +193,7 @@ pub const EthNamespace = struct {
193193
194194 /// eth_estimateGas - Estimates gas needed for a transaction
195195 pub fn estimateGas (self : EthNamespace , params : types.CallParams ) ! u64 {
196- const call_obj = try callParamsToJson (self .client .allocator , params );
196+ var call_obj = try callParamsToJson (self .client .allocator , params );
197197 defer call_obj .deinit ();
198198
199199 var rpc_params = [_ ]std.json.Value {
@@ -318,7 +318,7 @@ pub const EthNamespace = struct {
318318
319319 /// eth_getLogs - Returns an array of logs matching the filter
320320 pub fn getLogs (self : EthNamespace , filter : types.FilterOptions ) ! []Log {
321- const filter_obj = try filterOptionsToJson (self .client .allocator , filter );
321+ var filter_obj = try filterOptionsToJson (self .client .allocator , filter );
322322 defer filter_obj .deinit ();
323323
324324 var params = [_ ]std.json.Value {
@@ -337,7 +337,7 @@ pub const EthNamespace = struct {
337337 for (logs .items ) | log | {
338338 log .deinit ();
339339 }
340- logs .deinit ();
340+ logs .deinit (self . client . allocator );
341341 }
342342
343343 for (result .array .items ) | log_json | {
@@ -373,7 +373,7 @@ pub const EthNamespace = struct {
373373
374374 /// eth_sendTransaction - Creates and sends a transaction
375375 pub fn sendTransaction (self : EthNamespace , params : types.TransactionParams ) ! Hash {
376- const tx_obj = try transactionParamsToJson (self .client .allocator , params );
376+ var tx_obj = try transactionParamsToJson (self .client .allocator , params );
377377 defer tx_obj .deinit ();
378378
379379 var rpc_params = [_ ]std.json.Value {
@@ -537,7 +537,7 @@ pub const EthNamespace = struct {
537537
538538 /// eth_signTransaction - Signs a transaction
539539 pub fn signTransaction (self : EthNamespace , params : types.TransactionParams ) ! []u8 {
540- const tx_obj = try transactionParamsToJson (self .client .allocator , params );
540+ var tx_obj = try transactionParamsToJson (self .client .allocator , params );
541541 defer tx_obj .deinit ();
542542
543543 var rpc_params = [_ ]std.json.Value {
@@ -585,16 +585,36 @@ const JsonObjectWrapper = struct {
585585
586586 fn deinit (self : * JsonObjectWrapper ) void {
587587 if (self .value == .object ) {
588- // Free all string values that were allocated
588+ // Object keys here are string literals (never allocated); the
589+ // values are heap-allocated (hex strings, or arrays of them, as
590+ // in a `topics` filter). Recursively free the values so nested
591+ // arrays don't leak, then drop the map itself.
589592 var iter = self .value .object .iterator ();
590593 while (iter .next ()) | entry | {
591- if (entry .value_ptr .* == .string ) {
592- self .allocator .free (entry .value_ptr .string );
593- }
594+ freeValue (self .allocator , entry .value_ptr .* );
594595 }
595596 self .value .object .deinit (self .allocator );
596597 }
597598 }
599+
600+ /// Recursively free a JSON value's allocated data. Object keys are left
601+ /// untouched — this wrapper only ever builds objects with literal keys.
602+ fn freeValue (allocator : std.mem.Allocator , value : std.json.Value ) void {
603+ switch (value ) {
604+ .string = > | s | allocator .free (s ),
605+ .number_string = > | ns | allocator .free (ns ),
606+ .array = > | arr | {
607+ for (arr .items ) | item | freeValue (allocator , item );
608+ @constCast (& arr ).deinit ();
609+ },
610+ .object = > | obj | {
611+ var it = obj .iterator ();
612+ while (it .next ()) | e | freeValue (allocator , e .value_ptr .* );
613+ @constCast (& obj ).deinit (allocator );
614+ },
615+ else = > {},
616+ }
617+ }
598618};
599619
600620/// Convert CallParams to JSON object
@@ -1334,3 +1354,26 @@ test "call params to json" {
13341354 try std .testing .expect (json_obj .value .object .contains ("to" ));
13351355 try std .testing .expect (json_obj .value .object .contains ("gas" ));
13361356}
1357+
1358+ test "filter options with topics to json frees cleanly" {
1359+ // Guards the JsonObjectWrapper.deinit leak fix: the topics array's hex
1360+ // strings must be freed, not just top-level string members. Runs under
1361+ // testing.allocator, which fails the test on any leak.
1362+ const allocator = std .testing .allocator ;
1363+
1364+ const topic = Hash .fromBytes ([_ ]u8 {0xAB } ** 32 );
1365+ var topics = [_ ]? Hash {topic };
1366+ const filter = types.FilterOptions {
1367+ .from_block = .{ .number = 100 },
1368+ .to_block = .{ .number = 200 },
1369+ .address = Address .fromBytes ([_ ]u8 {0xCD } ** 20 ),
1370+ .topics = topics [0.. ],
1371+ };
1372+
1373+ var obj = try filterOptionsToJson (allocator , filter );
1374+ defer obj .deinit ();
1375+
1376+ try std .testing .expect (obj .value == .object );
1377+ try std .testing .expect (obj .value .object .contains ("topics" ));
1378+ try std .testing .expect (obj .value .object .contains ("address" ));
1379+ }
0 commit comments