1
+ use std:: { io, str:: FromStr } ;
2
+
1
3
use cast:: Cast ;
2
4
use clap:: Parser ;
3
- use ethers:: {
5
+ use ethers:: { providers:: Middleware , types:: NameOrAddress } ;
6
+ use ethers_core:: {
4
7
abi:: { Address , Event , RawTopicFilter , Topic , TopicFilter } ,
5
- providers:: Middleware ,
6
- types:: { BlockId , BlockNumber , Filter , FilterBlockOption , NameOrAddress , ValueOrArray , H256 } ,
8
+ types:: { BlockId , BlockNumber , Filter , FilterBlockOption , ValueOrArray , H256 } ,
7
9
} ;
8
10
use eyre:: Result ;
9
11
use foundry_cli:: { opts:: EthereumOpts , utils} ;
10
12
use foundry_common:: abi:: { get_event, parse_tokens} ;
11
13
use foundry_config:: Config ;
12
14
use itertools:: Itertools ;
13
- use std:: str:: FromStr ;
14
15
15
16
/// CLI arguments for `cast logs`.
16
17
#[ derive( Debug , Parser ) ]
@@ -44,7 +45,12 @@ pub struct LogsArgs {
44
45
#[ clap( value_name = "TOPICS_OR_ARGS" ) ]
45
46
topics_or_args : Vec < String > ,
46
47
47
- /// Print the logs as JSON.
48
+ /// If the RPC type and endpoints supports `eth_subscribe` stream logs instead of printing and
49
+ /// exiting. Will continue until interrupted or TO_BLOCK is reached.
50
+ #[ clap( long) ]
51
+ subscribe : bool ,
52
+
53
+ /// Print the logs as JSON.s
48
54
#[ clap( long, short, help_heading = "Display options" ) ]
49
55
json : bool ,
50
56
@@ -55,12 +61,21 @@ pub struct LogsArgs {
55
61
impl LogsArgs {
56
62
pub async fn run ( self ) -> Result < ( ) > {
57
63
let LogsArgs {
58
- from_block, to_block, address, topics_or_args, sig_or_topic, json, eth, ..
64
+ from_block,
65
+ to_block,
66
+ address,
67
+ sig_or_topic,
68
+ topics_or_args,
69
+ subscribe,
70
+ json,
71
+ eth,
59
72
} = self ;
60
73
61
74
let config = Config :: from ( & eth) ;
62
75
let provider = utils:: get_provider ( & config) ?;
63
76
77
+ let cast = Cast :: new ( & provider) ;
78
+
64
79
let address = match address {
65
80
Some ( address) => {
66
81
let address = match address {
@@ -72,48 +87,29 @@ impl LogsArgs {
72
87
None => None ,
73
88
} ;
74
89
75
- let from_block = convert_block_number ( & provider, from_block) . await ?;
76
- let to_block = convert_block_number ( & provider, to_block) . await ?;
77
-
78
- let cast = Cast :: new ( & provider) ;
90
+ let from_block = cast. convert_block_number ( from_block) . await ?;
91
+ let to_block = cast. convert_block_number ( to_block) . await ?;
79
92
80
93
let filter = build_filter ( from_block, to_block, address, sig_or_topic, topics_or_args) ?;
81
94
82
- let logs = cast. filter_logs ( filter, json) . await ?;
95
+ if !subscribe {
96
+ let logs = cast. filter_logs ( filter, json) . await ?;
83
97
84
- println ! ( "{}" , logs) ;
98
+ println ! ( "{}" , logs) ;
85
99
86
- Ok ( ( ) )
87
- }
88
- }
100
+ return Ok ( ( ) )
101
+ }
89
102
90
- /// Converts a block identifier into a block number.
91
- ///
92
- /// If the block identifier is a block number, then this function returns the block number. If the
93
- /// block identifier is a block hash, then this function returns the block number of that block
94
- /// hash. If the block identifier is `None`, then this function returns `None`.
95
- async fn convert_block_number < M : Middleware > (
96
- provider : M ,
97
- block : Option < BlockId > ,
98
- ) -> Result < Option < BlockNumber > , eyre:: Error >
99
- where
100
- M :: Error : ' static ,
101
- {
102
- match block {
103
- Some ( block) => match block {
104
- BlockId :: Number ( block_number) => Ok ( Some ( block_number) ) ,
105
- BlockId :: Hash ( hash) => {
106
- let block = provider. get_block ( hash) . await ?;
107
- Ok ( block. map ( |block| block. number . unwrap ( ) ) . map ( BlockNumber :: from) )
108
- }
109
- } ,
110
- None => Ok ( None ) ,
103
+ let mut stdout = io:: stdout ( ) ;
104
+ cast. subscribe ( filter, & mut stdout, json) . await ?;
105
+
106
+ Ok ( ( ) )
111
107
}
112
108
}
113
109
114
- // First tries to parse the `sig_or_topic` as an event signature. If successful, `topics_or_args` is
115
- // parsed as indexed inputs and converted to topics. Otherwise, `sig_or_topic` is prepended to
116
- // `topics_or_args` and used as raw topics.
110
+ /// Builds a Filter by first trying to parse the `sig_or_topic` as an event signature. If
111
+ /// successful, `topics_or_args` is parsed as indexed inputs and converted to topics. Otherwise,
112
+ /// `sig_or_topic` is prepended to `topics_or_args` and used as raw topics.
117
113
fn build_filter (
118
114
from_block : Option < BlockNumber > ,
119
115
to_block : Option < BlockNumber > ,
@@ -154,7 +150,7 @@ fn build_filter(
154
150
Ok ( filter)
155
151
}
156
152
157
- // Creates a TopicFilter for the given event signature and arguments.
153
+ /// Creates a TopicFilter from the given event signature and arguments.
158
154
fn build_filter_event_sig ( event : Event , args : Vec < String > ) -> Result < TopicFilter , eyre:: Error > {
159
155
let args = args. iter ( ) . map ( |arg| arg. as_str ( ) ) . collect :: < Vec < _ > > ( ) ;
160
156
@@ -195,7 +191,7 @@ fn build_filter_event_sig(event: Event, args: Vec<String>) -> Result<TopicFilter
195
191
Ok ( event. filter ( raw) ?)
196
192
}
197
193
198
- // Creates a TopicFilter from raw topic hashes.
194
+ /// Creates a TopicFilter from raw topic hashes.
199
195
fn build_filter_topics ( topics : Vec < String > ) -> Result < TopicFilter , eyre:: Error > {
200
196
let mut topics = topics
201
197
. into_iter ( )
@@ -214,8 +210,11 @@ fn build_filter_topics(topics: Vec<String>) -> Result<TopicFilter, eyre::Error>
214
210
215
211
#[ cfg( test) ]
216
212
mod tests {
213
+ use std:: str:: FromStr ;
214
+
217
215
use super :: * ;
218
216
use ethers:: types:: H160 ;
217
+ use ethers_core:: types:: H256 ;
219
218
220
219
const ADDRESS : & str = "0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38" ;
221
220
const TRANSFER_SIG : & str = "Transfer(address indexed,address indexed,uint256)" ;
0 commit comments