cast/cmd/
erc20.rs

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/// Interact with ERC20 tokens.
38#[derive(Debug, Parser, Clone)]
39pub enum Erc20Subcommand {
40    /// Query ERC20 token balance.
41    #[command(visible_alias = "b")]
42    Balance {
43        /// The ERC20 token contract address.
44        #[arg(value_parser = NameOrAddress::from_str)]
45        token: NameOrAddress,
46
47        /// The owner to query balance for.
48        #[arg(value_parser = NameOrAddress::from_str)]
49        owner: NameOrAddress,
50
51        /// The block height to query at.
52        #[arg(long, short = 'B')]
53        block: Option<BlockId>,
54
55        #[command(flatten)]
56        rpc: RpcOpts,
57    },
58
59    /// Transfer ERC20 tokens.
60    #[command(visible_alias = "t")]
61    Transfer {
62        /// The ERC20 token contract address.
63        #[arg(value_parser = NameOrAddress::from_str)]
64        token: NameOrAddress,
65
66        /// The recipient address.
67        #[arg(value_parser = NameOrAddress::from_str)]
68        to: NameOrAddress,
69
70        /// The amount to transfer.
71        amount: String,
72
73        #[command(flatten)]
74        send_tx: SendTxOpts,
75    },
76
77    /// Approve ERC20 token spending.
78    #[command(visible_alias = "a")]
79    Approve {
80        /// The ERC20 token contract address.
81        #[arg(value_parser = NameOrAddress::from_str)]
82        token: NameOrAddress,
83
84        /// The spender address.
85        #[arg(value_parser = NameOrAddress::from_str)]
86        spender: NameOrAddress,
87
88        /// The amount to approve.
89        amount: String,
90
91        #[command(flatten)]
92        send_tx: SendTxOpts,
93    },
94
95    /// Query ERC20 token allowance.
96    #[command(visible_alias = "al")]
97    Allowance {
98        /// The ERC20 token contract address.
99        #[arg(value_parser = NameOrAddress::from_str)]
100        token: NameOrAddress,
101
102        /// The owner address.
103        #[arg(value_parser = NameOrAddress::from_str)]
104        owner: NameOrAddress,
105
106        /// The spender address.
107        #[arg(value_parser = NameOrAddress::from_str)]
108        spender: NameOrAddress,
109
110        /// The block height to query at.
111        #[arg(long, short = 'B')]
112        block: Option<BlockId>,
113
114        #[command(flatten)]
115        rpc: RpcOpts,
116    },
117
118    /// Query ERC20 token name.
119    #[command(visible_alias = "n")]
120    Name {
121        /// The ERC20 token contract address.
122        #[arg(value_parser = NameOrAddress::from_str)]
123        token: NameOrAddress,
124
125        /// The block height to query at.
126        #[arg(long, short = 'B')]
127        block: Option<BlockId>,
128
129        #[command(flatten)]
130        rpc: RpcOpts,
131    },
132
133    /// Query ERC20 token symbol.
134    #[command(visible_alias = "s")]
135    Symbol {
136        /// The ERC20 token contract address.
137        #[arg(value_parser = NameOrAddress::from_str)]
138        token: NameOrAddress,
139
140        /// The block height to query at.
141        #[arg(long, short = 'B')]
142        block: Option<BlockId>,
143
144        #[command(flatten)]
145        rpc: RpcOpts,
146    },
147
148    /// Query ERC20 token decimals.
149    #[command(visible_alias = "d")]
150    Decimals {
151        /// The ERC20 token contract address.
152        #[arg(value_parser = NameOrAddress::from_str)]
153        token: NameOrAddress,
154
155        /// The block height to query at.
156        #[arg(long, short = 'B')]
157        block: Option<BlockId>,
158
159        #[command(flatten)]
160        rpc: RpcOpts,
161    },
162
163    /// Query ERC20 token total supply.
164    #[command(visible_alias = "ts")]
165    TotalSupply {
166        /// The ERC20 token contract address.
167        #[arg(value_parser = NameOrAddress::from_str)]
168        token: NameOrAddress,
169
170        /// The block height to query at.
171        #[arg(long, short = 'B')]
172        block: Option<BlockId>,
173
174        #[command(flatten)]
175        rpc: RpcOpts,
176    },
177
178    /// Mint ERC20 tokens (if the token supports minting).
179    #[command(visible_alias = "m")]
180    Mint {
181        /// The ERC20 token contract address.
182        #[arg(value_parser = NameOrAddress::from_str)]
183        token: NameOrAddress,
184
185        /// The recipient address.
186        #[arg(value_parser = NameOrAddress::from_str)]
187        to: NameOrAddress,
188
189        /// The amount to mint.
190        amount: String,
191
192        #[command(flatten)]
193        send_tx: SendTxOpts,
194    },
195
196    /// Burn ERC20 tokens.
197    #[command(visible_alias = "bu")]
198    Burn {
199        /// The ERC20 token contract address.
200        #[arg(value_parser = NameOrAddress::from_str)]
201        token: NameOrAddress,
202
203        /// The amount to burn.
204        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            // Read-only
232            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            // State-changing
303            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}