cast/cmd/
erc20.rs

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