1use foundry_common::fmt::format_uint_exp;
2use std::str::FromStr;
3
4use crate::{
5 cmd::{call_overrides::CallOverrideOpts, rpc_provider, send::SendTxArgs},
6 tx::{SendTxOpts, TxParams},
7};
8use alloy_eips::BlockId;
9use alloy_ens::NameOrAddress;
10use alloy_network::AnyNetwork;
11use alloy_primitives::{Address, U256};
12use alloy_sol_types::{SolCall, sol};
13use clap::Parser;
14use eyre::Result;
15use foundry_cli::{
16 json::{print_json_success, print_scalar},
17 opts::RpcOpts,
18};
19use foundry_common::{provider::RetryProvider, shell};
20
21mod permit;
22
23sol! {
24 #[sol(rpc)]
25 interface IERC20 {
26 event Transfer(address indexed from, address indexed to, uint256 value);
27
28 function name() external view returns (string);
29 function symbol() external view returns (string);
30 function decimals() external view returns (uint8);
31 function totalSupply() external view returns (uint256);
32 function balanceOf(address owner) external view returns (uint256);
33 function transfer(address to, uint256 amount) external returns (bool);
34 function approve(address spender, uint256 amount) external returns (bool);
35 function allowance(address owner, address spender) external view returns (uint256);
36 function mint(address to, uint256 amount) external;
37 function burn(uint256 amount) external;
38 }
39}
40
41#[derive(Debug, Parser, Clone)]
43pub enum Erc20Subcommand {
44 #[command(verbatim_doc_comment)]
70 Permit(permit::PermitArgs),
71
72 #[command(visible_alias = "b")]
74 Balance {
75 #[arg(value_parser = NameOrAddress::from_str)]
77 token: NameOrAddress,
78
79 #[arg(value_parser = NameOrAddress::from_str)]
81 owner: NameOrAddress,
82
83 #[arg(long, short = 'B')]
85 block: Option<BlockId>,
86
87 #[command(flatten)]
88 rpc: RpcOpts,
89
90 #[command(flatten)]
91 overrides: CallOverrideOpts,
92 },
93
94 #[command(visible_aliases = ["t", "send"])]
96 Transfer {
97 #[arg(value_parser = NameOrAddress::from_str)]
99 token: NameOrAddress,
100
101 #[arg(value_parser = NameOrAddress::from_str)]
103 to: NameOrAddress,
104
105 amount: String,
107
108 #[command(flatten)]
109 send_tx: SendTxOpts,
110
111 #[command(flatten)]
112 tx: TxParams,
113 },
114
115 #[command(visible_alias = "a")]
117 Approve {
118 #[arg(value_parser = NameOrAddress::from_str)]
120 token: NameOrAddress,
121
122 #[arg(value_parser = NameOrAddress::from_str)]
124 spender: NameOrAddress,
125
126 amount: String,
128
129 #[command(flatten)]
130 send_tx: SendTxOpts,
131
132 #[command(flatten)]
133 tx: TxParams,
134 },
135
136 #[command(visible_alias = "al")]
138 Allowance {
139 #[arg(value_parser = NameOrAddress::from_str)]
141 token: NameOrAddress,
142
143 #[arg(value_parser = NameOrAddress::from_str)]
145 owner: NameOrAddress,
146
147 #[arg(value_parser = NameOrAddress::from_str)]
149 spender: NameOrAddress,
150
151 #[arg(long, short = 'B')]
153 block: Option<BlockId>,
154
155 #[command(flatten)]
156 rpc: RpcOpts,
157 },
158
159 #[command(visible_alias = "n")]
161 Name {
162 #[arg(value_parser = NameOrAddress::from_str)]
164 token: NameOrAddress,
165
166 #[arg(long, short = 'B')]
168 block: Option<BlockId>,
169
170 #[command(flatten)]
171 rpc: RpcOpts,
172 },
173
174 #[command(visible_alias = "s")]
176 Symbol {
177 #[arg(value_parser = NameOrAddress::from_str)]
179 token: NameOrAddress,
180
181 #[arg(long, short = 'B')]
183 block: Option<BlockId>,
184
185 #[command(flatten)]
186 rpc: RpcOpts,
187 },
188
189 #[command(visible_alias = "d")]
191 Decimals {
192 #[arg(value_parser = NameOrAddress::from_str)]
194 token: NameOrAddress,
195
196 #[arg(long, short = 'B')]
198 block: Option<BlockId>,
199
200 #[command(flatten)]
201 rpc: RpcOpts,
202 },
203
204 #[command(visible_alias = "ts")]
206 TotalSupply {
207 #[arg(value_parser = NameOrAddress::from_str)]
209 token: NameOrAddress,
210
211 #[arg(long, short = 'B')]
213 block: Option<BlockId>,
214
215 #[command(flatten)]
216 rpc: RpcOpts,
217 },
218
219 #[command(visible_alias = "m")]
221 Mint {
222 #[arg(value_parser = NameOrAddress::from_str)]
224 token: NameOrAddress,
225
226 #[arg(value_parser = NameOrAddress::from_str)]
228 to: NameOrAddress,
229
230 amount: String,
232
233 #[command(flatten)]
234 send_tx: SendTxOpts,
235
236 #[command(flatten)]
237 tx: TxParams,
238 },
239
240 #[command(visible_alias = "bu")]
242 Burn {
243 #[arg(value_parser = NameOrAddress::from_str)]
245 token: NameOrAddress,
246
247 amount: String,
249
250 #[command(flatten)]
251 send_tx: SendTxOpts,
252
253 #[command(flatten)]
254 tx: TxParams,
255 },
256}
257
258impl Erc20Subcommand {
259 pub async fn run(self) -> Result<()> {
260 match self {
261 Self::Permit(args) => args.run().await,
262 Self::Allowance { token, owner, spender, block, rpc } => {
263 let (provider, erc20) = token_at(&rpc, token).await?;
264 let owner = owner.resolve(&provider).await?;
265 let spender = spender.resolve(&provider).await?;
266 let allowance =
267 erc20.allowance(owner, spender).block(block.unwrap_or_default()).call().await?;
268 print_amount(allowance)
269 }
270 Self::Balance { token, owner, block, rpc, overrides } => {
271 let (provider, erc20) = token_at(&rpc, token).await?;
272 let owner = owner.resolve(&provider).await?;
273 let call = erc20.balanceOf(owner).block(block.unwrap_or_default());
274 let balance = overrides.apply(call.call())?.await?;
275 print_scalar(balance.to_string())
276 }
277 Self::Name { token, block, rpc } => {
278 let (_, erc20) = token_at(&rpc, token).await?;
279 print_scalar(erc20.name().block(block.unwrap_or_default()).call().await?)
280 }
281 Self::Symbol { token, block, rpc } => {
282 let (_, erc20) = token_at(&rpc, token).await?;
283 print_scalar(erc20.symbol().block(block.unwrap_or_default()).call().await?)
284 }
285 Self::Decimals { token, block, rpc } => {
286 let (_, erc20) = token_at(&rpc, token).await?;
287 print_scalar(erc20.decimals().block(block.unwrap_or_default()).call().await?)
288 }
289 Self::TotalSupply { token, block, rpc } => {
290 let (_, erc20) = token_at(&rpc, token).await?;
291 print_amount(erc20.totalSupply().block(block.unwrap_or_default()).call().await?)
292 }
293 Self::Transfer { token, to, amount, send_tx, tx } => {
294 let to = resolve(&send_tx.eth.rpc, to).await?;
295 let call = IERC20::transferCall { to, amount: U256::from_str(&amount)? };
296 send(token, call, send_tx, tx).await
297 }
298 Self::Approve { token, spender, amount, send_tx, tx } => {
299 let spender = resolve(&send_tx.eth.rpc, spender).await?;
300 let call = IERC20::approveCall { spender, amount: U256::from_str(&amount)? };
301 send(token, call, send_tx, tx).await
302 }
303 Self::Mint { token, to, amount, send_tx, tx } => {
304 let to = resolve(&send_tx.eth.rpc, to).await?;
305 let call = IERC20::mintCall { to, amount: U256::from_str(&amount)? };
306 send(token, call, send_tx, tx).await
307 }
308 Self::Burn { token, amount, send_tx, tx } => {
309 send(token, IERC20::burnCall { amount: U256::from_str(&amount)? }, send_tx, tx)
310 .await
311 }
312 }
313 }
314}
315
316async fn token_at(
317 rpc: &RpcOpts,
318 token: NameOrAddress,
319) -> Result<(RetryProvider, IERC20::IERC20Instance<RetryProvider, AnyNetwork>)> {
320 let provider = rpc_provider(rpc)?;
321 let token = token.resolve(&provider).await?;
322 Ok((provider.clone(), IERC20::new(token, provider)))
323}
324
325async fn resolve(rpc: &RpcOpts, account: NameOrAddress) -> Result<Address> {
326 Ok(account.resolve(&rpc_provider(rpc)?).await?)
327}
328
329async fn send(
330 token: NameOrAddress,
331 call: impl SolCall,
332 send_tx: SendTxOpts,
333 tx: TxParams,
334) -> Result<()> {
335 Box::pin(SendTxArgs::contract_call(token, call.abi_encode(), send_tx, tx).run()).await
337}
338
339pub(crate) fn print_amount(amount: U256) -> Result<()> {
341 if shell::is_json() {
342 print_json_success(amount.to_string())
343 } else {
344 sh_println!("{}", format_uint_exp(amount))
345 }
346}