1use crate::{
2 cmd::{
3 keychain::ensure_tempo_precompile_active,
4 tip20::{resolve_tip20_signer, send_tip20_transaction},
5 },
6 tempo::print_payload,
7 tx::{SendTxOpts, TxParams},
8};
9use alloy_ens::NameOrAddress;
10use clap::{Parser, ValueEnum};
11use eyre::Result;
12use foundry_cli::{opts::RpcOpts, utils::LoadConfig};
13use foundry_common::provider::ProviderBuilder;
14use foundry_evm::hardfork::TempoHardfork;
15use serde_json::json;
16use std::str::FromStr;
17use tempo_alloy::TempoNetwork;
18use tempo_contracts::precompiles::{IStorageCredits, STORAGE_CREDITS_ADDRESS};
19
20#[derive(Debug, Parser, Clone)]
26pub enum StorageCreditsSubcommand {
27 Balance {
29 #[arg(value_parser = NameOrAddress::from_str)]
31 account: NameOrAddress,
32
33 #[command(flatten)]
34 rpc: RpcOpts,
35 },
36
37 Mode {
42 #[arg(value_parser = NameOrAddress::from_str)]
44 account: NameOrAddress,
45
46 #[command(flatten)]
47 rpc: RpcOpts,
48 },
49
50 Budget {
55 #[arg(value_parser = NameOrAddress::from_str)]
57 account: NameOrAddress,
58
59 #[command(flatten)]
60 rpc: RpcOpts,
61 },
62
63 SetMode {
68 #[arg(value_enum)]
70 mode: CreditMode,
71
72 #[command(flatten)]
73 send_tx: SendTxOpts,
74
75 #[command(flatten)]
76 tx: TxParams,
77 },
78
79 SetBudget {
84 credits: u64,
86
87 #[command(flatten)]
88 send_tx: SendTxOpts,
89
90 #[command(flatten)]
91 tx: TxParams,
92 },
93}
94
95#[derive(Debug, Clone, Copy, ValueEnum)]
97pub enum CreditMode {
98 Refund,
100 Preserve,
102 Direct,
104}
105
106impl StorageCreditsSubcommand {
107 pub async fn run(self) -> Result<()> {
108 match self {
109 Self::Balance { account, rpc } => balance(account, rpc).await,
110 Self::Mode { account, rpc } => mode(account, rpc).await,
111 Self::Budget { account, rpc } => budget(account, rpc).await,
112 Self::SetMode { mode, send_tx, tx } => set_mode(mode, send_tx, tx).await,
113 Self::SetBudget { credits, send_tx, tx } => set_budget(credits, send_tx, tx).await,
114 }
115 }
116}
117
118async fn balance(account: NameOrAddress, rpc: RpcOpts) -> Result<()> {
119 let config = rpc.load_config()?;
120 let provider = ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
121 ensure_storage_credits_t7(&provider, "cast storage-credits balance").await?;
122 let account = account.resolve(&provider).await?;
123
124 let credits = IStorageCredits::new(STORAGE_CREDITS_ADDRESS, &provider);
125 let balance = credits.balanceOf(account).call().await?;
126 let payload = json!({ "account": format!("{account}"), "balance": balance });
127 print_payload(payload, |payload| {
128 sh_println!(
129 "Account: {}\nBalance: {}",
130 payload["account"].as_str().unwrap_or_default(),
131 payload["balance"],
132 )
133 })
134}
135
136async fn mode(account: NameOrAddress, rpc: RpcOpts) -> Result<()> {
137 let config = rpc.load_config()?;
138 let provider = ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
139 ensure_storage_credits_t7(&provider, "cast storage-credits mode").await?;
140 let account = account.resolve(&provider).await?;
141
142 let credits = IStorageCredits::new(STORAGE_CREDITS_ADDRESS, &provider);
143 let mode = credits.modeOf(account).call().await?;
144 let payload = json!({ "account": format!("{account}"), "mode": mode.as_str() });
145 print_payload(payload, |payload| {
146 sh_println!(
147 "Account: {}\nMode: {}",
148 payload["account"].as_str().unwrap_or_default(),
149 payload["mode"].as_str().unwrap_or_default(),
150 )
151 })
152}
153
154async fn budget(account: NameOrAddress, rpc: RpcOpts) -> Result<()> {
155 let config = rpc.load_config()?;
156 let provider = ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
157 ensure_storage_credits_t7(&provider, "cast storage-credits budget").await?;
158 let account = account.resolve(&provider).await?;
159
160 let credits = IStorageCredits::new(STORAGE_CREDITS_ADDRESS, &provider);
161 let budget = credits.budgetOf(account).call().await?;
162 let payload = json!({ "account": format!("{account}"), "budget": budget });
163 print_payload(payload, |payload| {
164 sh_println!(
165 "Account: {}\nBudget: {}",
166 payload["account"].as_str().unwrap_or_default(),
167 payload["budget"],
168 )
169 })
170}
171
172async fn set_mode(mode: CreditMode, send_tx: SendTxOpts, tx: TxParams) -> Result<()> {
173 ensure_send_storage_credits_t7(&send_tx, "cast storage-credits set-mode").await?;
174 let (signer, access_key) = resolve_tip20_signer(&send_tx, &tx).await?;
175 let mode_arg = (mode.to_sol() as u8).to_string();
177 send_tip20_transaction(
178 NameOrAddress::Address(STORAGE_CREDITS_ADDRESS),
179 "setMode(uint8)",
180 vec![mode_arg],
181 send_tx,
182 tx,
183 signer,
184 access_key,
185 )
186 .await
187}
188
189async fn set_budget(credits: u64, send_tx: SendTxOpts, tx: TxParams) -> Result<()> {
190 ensure_send_storage_credits_t7(&send_tx, "cast storage-credits set-budget").await?;
191 let (signer, access_key) = resolve_tip20_signer(&send_tx, &tx).await?;
192 send_tip20_transaction(
193 NameOrAddress::Address(STORAGE_CREDITS_ADDRESS),
194 "setBudget(uint64)",
195 vec![credits.to_string()],
196 send_tx,
197 tx,
198 signer,
199 access_key,
200 )
201 .await
202}
203
204async fn ensure_storage_credits_t7<P>(provider: &P, command: &str) -> Result<()>
207where
208 P: alloy_provider::Provider<TempoNetwork>,
209{
210 ensure_tempo_precompile_active(
211 provider,
212 TempoHardfork::T7,
213 STORAGE_CREDITS_ADDRESS,
214 &format!("{command} requires a Tempo T7-capable StorageCredits RPC"),
215 )
216 .await
217}
218
219async fn ensure_send_storage_credits_t7(send_tx: &SendTxOpts, command: &str) -> Result<()> {
222 let config = send_tx.eth.rpc.load_config()?;
223 let provider = ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
224 ensure_storage_credits_t7(&provider, command).await
225}
226
227impl CreditMode {
228 const fn to_sol(self) -> IStorageCredits::Mode {
229 match self {
230 Self::Refund => IStorageCredits::Mode::Refund,
231 Self::Preserve => IStorageCredits::Mode::Preserve,
232 Self::Direct => IStorageCredits::Mode::Direct,
233 }
234 }
235}