1use std::str::FromStr;
2
3use crate::{
4 cmd::send::cast_send,
5 format_uint_exp,
6 tx::{SendTxOpts, signing_provider},
7};
8use alloy_eips::BlockId;
9use alloy_ens::NameOrAddress;
10use alloy_primitives::U256;
11use alloy_sol_types::sol;
12use clap::Parser;
13use foundry_cli::{
14 opts::RpcOpts,
15 utils::{LoadConfig, get_provider},
16};
17#[doc(hidden)]
18pub use foundry_config::utils::*;
19
20sol! {
21 #[sol(rpc)]
22 interface IERC20 {
23 #[derive(Debug)]
24 function name() external view returns (string);
25 function symbol() external view returns (string);
26 function decimals() external view returns (uint8);
27 function totalSupply() external view returns (uint256);
28 function balanceOf(address owner) external view returns (uint256);
29 function transfer(address to, uint256 amount) external returns (bool);
30 function approve(address spender, uint256 amount) external returns (bool);
31 function allowance(address owner, address spender) external view returns (uint256);
32 function mint(address to, uint256 amount) external;
33 function burn(uint256 amount) external;
34 }
35}
36
37#[derive(Debug, Parser, Clone)]
39pub enum Erc20Subcommand {
40 #[command(visible_alias = "b")]
42 Balance {
43 #[arg(value_parser = NameOrAddress::from_str)]
45 token: NameOrAddress,
46
47 #[arg(value_parser = NameOrAddress::from_str)]
49 owner: NameOrAddress,
50
51 #[arg(long, short = 'B')]
53 block: Option<BlockId>,
54
55 #[command(flatten)]
56 rpc: RpcOpts,
57 },
58
59 #[command(visible_alias = "t")]
61 Transfer {
62 #[arg(value_parser = NameOrAddress::from_str)]
64 token: NameOrAddress,
65
66 #[arg(value_parser = NameOrAddress::from_str)]
68 to: NameOrAddress,
69
70 amount: String,
72
73 #[command(flatten)]
74 send_tx: SendTxOpts,
75 },
76
77 #[command(visible_alias = "a")]
79 Approve {
80 #[arg(value_parser = NameOrAddress::from_str)]
82 token: NameOrAddress,
83
84 #[arg(value_parser = NameOrAddress::from_str)]
86 spender: NameOrAddress,
87
88 amount: String,
90
91 #[command(flatten)]
92 send_tx: SendTxOpts,
93 },
94
95 #[command(visible_alias = "al")]
97 Allowance {
98 #[arg(value_parser = NameOrAddress::from_str)]
100 token: NameOrAddress,
101
102 #[arg(value_parser = NameOrAddress::from_str)]
104 owner: NameOrAddress,
105
106 #[arg(value_parser = NameOrAddress::from_str)]
108 spender: NameOrAddress,
109
110 #[arg(long, short = 'B')]
112 block: Option<BlockId>,
113
114 #[command(flatten)]
115 rpc: RpcOpts,
116 },
117
118 #[command(visible_alias = "n")]
120 Name {
121 #[arg(value_parser = NameOrAddress::from_str)]
123 token: NameOrAddress,
124
125 #[arg(long, short = 'B')]
127 block: Option<BlockId>,
128
129 #[command(flatten)]
130 rpc: RpcOpts,
131 },
132
133 #[command(visible_alias = "s")]
135 Symbol {
136 #[arg(value_parser = NameOrAddress::from_str)]
138 token: NameOrAddress,
139
140 #[arg(long, short = 'B')]
142 block: Option<BlockId>,
143
144 #[command(flatten)]
145 rpc: RpcOpts,
146 },
147
148 #[command(visible_alias = "d")]
150 Decimals {
151 #[arg(value_parser = NameOrAddress::from_str)]
153 token: NameOrAddress,
154
155 #[arg(long, short = 'B')]
157 block: Option<BlockId>,
158
159 #[command(flatten)]
160 rpc: RpcOpts,
161 },
162
163 #[command(visible_alias = "ts")]
165 TotalSupply {
166 #[arg(value_parser = NameOrAddress::from_str)]
168 token: NameOrAddress,
169
170 #[arg(long, short = 'B')]
172 block: Option<BlockId>,
173
174 #[command(flatten)]
175 rpc: RpcOpts,
176 },
177
178 #[command(visible_alias = "m")]
180 Mint {
181 #[arg(value_parser = NameOrAddress::from_str)]
183 token: NameOrAddress,
184
185 #[arg(value_parser = NameOrAddress::from_str)]
187 to: NameOrAddress,
188
189 amount: String,
191
192 #[command(flatten)]
193 send_tx: SendTxOpts,
194 },
195
196 #[command(visible_alias = "bu")]
198 Burn {
199 #[arg(value_parser = NameOrAddress::from_str)]
201 token: NameOrAddress,
202
203 amount: String,
205
206 #[command(flatten)]
207 send_tx: SendTxOpts,
208 },
209}
210
211impl Erc20Subcommand {
212 fn rpc(&self) -> &RpcOpts {
213 match self {
214 Self::Allowance { rpc, .. } => rpc,
215 Self::Approve { send_tx, .. } => &send_tx.eth.rpc,
216 Self::Balance { rpc, .. } => rpc,
217 Self::Transfer { send_tx, .. } => &send_tx.eth.rpc,
218 Self::Name { rpc, .. } => rpc,
219 Self::Symbol { rpc, .. } => rpc,
220 Self::Decimals { rpc, .. } => rpc,
221 Self::TotalSupply { rpc, .. } => rpc,
222 Self::Mint { send_tx, .. } => &send_tx.eth.rpc,
223 Self::Burn { send_tx, .. } => &send_tx.eth.rpc,
224 }
225 }
226
227 pub async fn run(self) -> eyre::Result<()> {
228 let config = self.rpc().load_config()?;
229
230 match self {
231 Self::Allowance { token, owner, spender, block, .. } => {
233 let provider = get_provider(&config)?;
234 let token = token.resolve(&provider).await?;
235 let owner = owner.resolve(&provider).await?;
236 let spender = spender.resolve(&provider).await?;
237
238 let allowance = IERC20::new(token, &provider)
239 .allowance(owner, spender)
240 .block(block.unwrap_or_default())
241 .call()
242 .await?;
243
244 sh_println!("{}", format_uint_exp(allowance))?
245 }
246 Self::Balance { token, owner, block, .. } => {
247 let provider = get_provider(&config)?;
248 let token = token.resolve(&provider).await?;
249 let owner = owner.resolve(&provider).await?;
250
251 let balance = IERC20::new(token, &provider)
252 .balanceOf(owner)
253 .block(block.unwrap_or_default())
254 .call()
255 .await?;
256 sh_println!("{}", format_uint_exp(balance))?
257 }
258 Self::Name { token, block, .. } => {
259 let provider = get_provider(&config)?;
260 let token = token.resolve(&provider).await?;
261
262 let name = IERC20::new(token, &provider)
263 .name()
264 .block(block.unwrap_or_default())
265 .call()
266 .await?;
267 sh_println!("{}", name)?
268 }
269 Self::Symbol { token, block, .. } => {
270 let provider = get_provider(&config)?;
271 let token = token.resolve(&provider).await?;
272
273 let symbol = IERC20::new(token, &provider)
274 .symbol()
275 .block(block.unwrap_or_default())
276 .call()
277 .await?;
278 sh_println!("{}", symbol)?
279 }
280 Self::Decimals { token, block, .. } => {
281 let provider = get_provider(&config)?;
282 let token = token.resolve(&provider).await?;
283
284 let decimals = IERC20::new(token, &provider)
285 .decimals()
286 .block(block.unwrap_or_default())
287 .call()
288 .await?;
289 sh_println!("{}", decimals)?
290 }
291 Self::TotalSupply { token, block, .. } => {
292 let provider = get_provider(&config)?;
293 let token = token.resolve(&provider).await?;
294
295 let total_supply = IERC20::new(token, &provider)
296 .totalSupply()
297 .block(block.unwrap_or_default())
298 .call()
299 .await?;
300 sh_println!("{}", format_uint_exp(total_supply))?
301 }
302 Self::Transfer { token, to, amount, send_tx, .. } => {
304 let provider = signing_provider(&send_tx).await?;
305 let tx = IERC20::new(token.resolve(&provider).await?, &provider)
306 .transfer(to.resolve(&provider).await?, U256::from_str(&amount)?)
307 .into_transaction_request();
308 cast_send(
309 provider,
310 tx,
311 send_tx.cast_async,
312 send_tx.sync,
313 send_tx.confirmations,
314 send_tx.timeout.unwrap_or(config.transaction_timeout),
315 )
316 .await?
317 }
318 Self::Approve { token, spender, amount, send_tx, .. } => {
319 let provider = signing_provider(&send_tx).await?;
320 let tx = IERC20::new(token.resolve(&provider).await?, &provider)
321 .approve(spender.resolve(&provider).await?, U256::from_str(&amount)?)
322 .into_transaction_request();
323 cast_send(
324 provider,
325 tx,
326 send_tx.cast_async,
327 send_tx.sync,
328 send_tx.confirmations,
329 send_tx.timeout.unwrap_or(config.transaction_timeout),
330 )
331 .await?
332 }
333 Self::Mint { token, to, amount, send_tx, .. } => {
334 let provider = signing_provider(&send_tx).await?;
335 let tx = IERC20::new(token.resolve(&provider).await?, &provider)
336 .mint(to.resolve(&provider).await?, U256::from_str(&amount)?)
337 .into_transaction_request();
338 cast_send(
339 provider,
340 tx,
341 send_tx.cast_async,
342 send_tx.sync,
343 send_tx.confirmations,
344 send_tx.timeout.unwrap_or(config.transaction_timeout),
345 )
346 .await?
347 }
348 Self::Burn { token, amount, send_tx, .. } => {
349 let provider = signing_provider(&send_tx).await?;
350 let tx = IERC20::new(token.resolve(&provider).await?, &provider)
351 .burn(U256::from_str(&amount)?)
352 .into_transaction_request();
353 cast_send(
354 provider,
355 tx,
356 send_tx.cast_async,
357 send_tx.sync,
358 send_tx.confirmations,
359 send_tx.timeout.unwrap_or(config.transaction_timeout),
360 )
361 .await?
362 }
363 };
364 Ok(())
365 }
366}