-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathcli.rs
More file actions
377 lines (317 loc) · 13.8 KB
/
cli.rs
File metadata and controls
377 lines (317 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
use {
clap::{
builder::{PossibleValuesParser, TypedValueParser},
ArgGroup, ArgMatches, Args, Parser, Subcommand,
},
solana_clap_v3_utils::{
input_parsers::{
parse_url_or_moniker,
signer::{SignerSource, SignerSourceParserBuilder},
Amount,
},
keypair::pubkey_from_path,
},
solana_cli_output::OutputFormat,
solana_pubkey::Pubkey,
spl_single_pool::{self, find_pool_address},
};
#[derive(Clone, Debug, Parser)]
#[clap(author, version, about, long_about = None)]
pub struct Cli {
/// Configuration file to use
#[clap(global(true), short = 'C', long = "config", id = "PATH")]
pub config_file: Option<String>,
/// Show additional information
#[clap(global(true), short, long)]
pub verbose: bool,
/// Simulate transaction instead of executing
#[clap(global(true), long, alias = "dryrun")]
pub dry_run: bool,
/// URL for Solana JSON RPC or moniker (or their first letter):
/// [mainnet-beta, testnet, devnet, localhost].
/// Default from the configuration file.
#[clap(
global(true),
short = 'u',
long = "url",
id = "URL_OR_MONIKER",
value_parser = parse_url_or_moniker,
)]
pub json_rpc_url: Option<String>,
/// Specify the fee-payer account. This may be a keypair file, the ASK
/// keyword or the pubkey of an offline signer, provided an appropriate
/// --signer argument is also passed. Defaults to the client keypair.
#[clap(
global(true),
long,
id = "PAYER_KEYPAIR",
value_parser = SignerSourceParserBuilder::default().allow_all().build(),
)]
pub fee_payer: Option<SignerSource>,
/// Return information in specified output format
#[clap(
global(true),
long = "output",
id = "FORMAT",
conflicts_with = "verbose",
value_parser = PossibleValuesParser::new(["json", "json-compact"]).map(|o| parse_output_format(&o)),
)]
pub output_format: Option<OutputFormat>,
#[clap(subcommand)]
pub command: Command,
}
#[derive(Clone, Debug, Subcommand)]
pub enum Command {
/// Commands used to initialize or manage existing single-validator stake
/// pools. Other than initializing new pools, most users should never
/// need to use these.
Manage(ManageCli),
/// Deposit delegated stake into a pool in exchange for pool tokens, closing
/// out the original stake account. Pool address is inferred from stake account.
Deposit(DepositCli),
/// Withdraw stake into a new stake account, burning tokens in exchange.
/// Provide either pool or vote account address, plus either an amount of
/// tokens to burn or the ALL keyword to burn all.
Withdraw(WithdrawCli),
/// Display info for one or all single-validator stake pool(s)
Display(DisplayCli),
/// Deposit liquid sol into a pool in exchange for pool tokens, less a one percent
/// fee.
DepositSol(DepositSolCli),
}
#[derive(Clone, Debug, Parser)]
pub struct ManageCli {
#[clap(subcommand)]
pub manage: ManageCommand,
}
#[derive(Clone, Debug, Subcommand)]
pub enum ManageCommand {
/// Permissionlessly create the single-validator stake pool for a given
/// validator vote account if one does not already exist. The fee payer
/// also pays rent-exemption for accounts, along with the
/// cluster-configured minimum stake delegation
Initialize(InitializeCli),
/// Permissionlessly re-stake the main pool stake account if it was
/// deactivated from a delinquent validator, move active stake from the
/// on-ramp account into the main account, and move and delegate excess
/// lamports from the main account in the on-ramp account.
ReplenishPool(ReplenishCli),
/// Permissionlessly create default MPL token metadata for the pool mint.
/// Normally this is done automatically upon initialization, so this
/// does not need to be called.
CreateTokenMetadata(CreateMetadataCli),
/// Modify the MPL token metadata associated with the pool mint. This action
/// can only be performed by the validator vote account's withdraw
/// authority
UpdateTokenMetadata(UpdateMetadataCli),
/// Permissionlessly create the on-ramp account for an existing single-
/// validator stake pool, necessary for calling `ReplenishPool`.
/// This does NOT need to be called after `Initialize`: initialization
/// takes care of this in `>=v2.0.0`. Only existing pools created by
/// `1.0.x` need to to create the on-ramp explicitly.
CreateOnRamp(CreateOnRampCli),
}
#[derive(Clone, Debug, Args)]
pub struct InitializeCli {
/// The vote account to create the pool for
#[clap(value_parser = |p: &str| parse_address(p, "vote_account_address"))]
pub vote_account_address: Pubkey,
/// Do not create MPL metadata for the pool mint
#[clap(long)]
pub skip_metadata: bool,
}
#[derive(Clone, Debug, Args)]
#[clap(group(pool_source_group()))]
pub struct ReplenishCli {
/// The pool to replenish
#[clap(short, long = "pool", value_parser = |p: &str| parse_address(p, "pool_address"))]
pub pool_address: Option<Pubkey>,
/// The vote account corresponding to the pool to replenish
#[clap(long = "vote-account", value_parser = |p: &str| parse_address(p, "vote_account_address"))]
pub vote_account_address: Option<Pubkey>,
}
#[derive(Clone, Debug, Args)]
#[clap(group(pool_source_group().required(false)))]
pub struct DepositCli {
/// The stake account to deposit from. Must be in the same activation state
/// as the pool's stake account
#[clap(value_parser = |p: &str| parse_address(p, "stake_account_address"))]
pub stake_account_address: Pubkey,
/// The pool to deposit into. Optional for validation
#[clap(short, long = "pool", value_parser = |p: &str| parse_address(p, "pool_address"))]
pub pool_address: Option<Pubkey>,
/// The vote account corresponding to the pool to deposit into. Optional for validation
#[clap(long = "vote-account", value_parser = |p: &str| parse_address(p, "vote_account_address"))]
pub vote_account_address: Option<Pubkey>,
/// Signing authority on the stake account to be deposited. Defaults to the
/// client keypair
#[clap(long = "withdraw-authority", id = "STAKE_WITHDRAW_AUTHORITY_KEYPAIR", value_parser = SignerSourceParserBuilder::default().allow_all().build(),)]
pub stake_withdraw_authority: Option<SignerSource>,
/// The token account to mint to. Defaults to the client keypair's
/// associated token account
#[clap(long = "token-account", value_parser = |p: &str| parse_address(p, "token_account_address"))]
pub token_account_address: Option<Pubkey>,
/// The wallet to refund stake account rent to. Defaults to the client
/// keypair's pubkey
#[clap(long = "recipient", value_parser = |p: &str| parse_address(p, "lamport_recipient_address"))]
pub lamport_recipient_address: Option<Pubkey>,
}
#[derive(Clone, Debug, Args)]
#[clap(group(pool_source_group()))]
pub struct WithdrawCli {
/// Amount of tokens to burn for withdrawal
#[clap(value_parser = Amount::parse_decimal_or_all)]
pub token_amount: Amount,
/// The token account to withdraw from. Defaults to the associated token
/// account for the pool mint
#[clap(long = "token-account", value_parser = |p: &str| parse_address(p, "token_account_address"))]
pub token_account_address: Option<Pubkey>,
/// The pool to withdraw from
#[clap(short, long = "pool", value_parser = |p: &str| parse_address(p, "pool_address"))]
pub pool_address: Option<Pubkey>,
/// The vote account corresponding to the pool to withdraw from
#[clap(long = "vote-account", value_parser = |p: &str| parse_address(p, "vote_account_address"))]
pub vote_account_address: Option<Pubkey>,
/// Signing authority on the token account. Defaults to the client keypair
#[clap(long = "token-authority", id = "TOKEN_AUTHORITY_KEYPAIR", value_parser = SignerSourceParserBuilder::default().allow_all().build())]
pub token_authority: Option<SignerSource>,
/// Authority to assign to the new stake account. Defaults to the pubkey of
/// the client keypair
#[clap(long = "stake-authority", value_parser = |p: &str| parse_address(p, "stake_authority_address"))]
pub stake_authority_address: Option<Pubkey>,
/// Deactivate stake account after withdrawal
#[clap(long)]
pub deactivate: bool,
}
#[derive(Clone, Debug, Args)]
#[clap(group(pool_source_group()))]
pub struct CreateMetadataCli {
/// The pool to create default MPL token metadata for
#[clap(short, long = "pool", value_parser = |p: &str| parse_address(p, "pool_address"))]
pub pool_address: Option<Pubkey>,
/// The vote account corresponding to the pool to create metadata for
#[clap(long = "vote-account", value_parser = |p: &str| parse_address(p, "vote_account_address"))]
pub vote_account_address: Option<Pubkey>,
}
#[derive(Clone, Debug, Args)]
#[clap(group(pool_source_group()))]
pub struct UpdateMetadataCli {
/// New name for the pool token
#[clap(validator = is_valid_token_name)]
pub token_name: String,
/// New ticker symbol for the pool token
#[clap(validator = is_valid_token_symbol)]
pub token_symbol: String,
/// Optional external URI for the pool token. Leaving this argument blank
/// will clear any existing value
#[clap(validator = is_valid_token_uri)]
pub token_uri: Option<String>,
/// The pool to change MPL token metadata for
#[clap(short, long = "pool", value_parser = |p: &str| parse_address(p, "pool_address"))]
pub pool_address: Option<Pubkey>,
/// The vote account corresponding to the pool to create metadata for
#[clap(long = "vote-account", value_parser = |p: &str| parse_address(p, "vote_account_address"))]
pub vote_account_address: Option<Pubkey>,
/// Authorized withdrawer for the vote account, to prove validator
/// ownership. Defaults to the client keypair
#[clap(long, id = "AUTHORIZED_WITHDRAWER_KEYPAIR", value_parser = SignerSourceParserBuilder::default().allow_all().build())]
pub authorized_withdrawer: Option<SignerSource>,
}
#[derive(Clone, Debug, Args)]
#[clap(group(pool_source_group().arg("all")))]
pub struct DisplayCli {
/// The pool to display
#[clap(value_parser = |p: &str| parse_address(p, "pool_address"))]
pub pool_address: Option<Pubkey>,
/// The vote account corresponding to the pool to display
#[clap(long = "vote-account", value_parser = |p: &str| parse_address(p, "vote_account_address"))]
pub vote_account_address: Option<Pubkey>,
/// Display all pools
#[clap(long)]
pub all: bool,
}
#[derive(Clone, Debug, Args)]
#[clap(group(pool_source_group()))]
pub struct CreateOnRampCli {
/// The pool to create the on-ramp stake account for
#[clap(short, long = "pool", value_parser = |p: &str| parse_address(p, "pool_address"))]
pub pool_address: Option<Pubkey>,
/// The vote account corresponding to the pool to create the on-ramp for
#[clap(long = "vote-account", value_parser = |p: &str| parse_address(p, "vote_account_address"))]
pub vote_account_address: Option<Pubkey>,
}
#[derive(Clone, Debug, Args)]
#[clap(group(pool_source_group()))]
pub struct DepositSolCli {
/// Lamports to deposit into pool
pub lamports: u64,
/// The pool to deposit into
#[clap(short, long = "pool", value_parser = |p: &str| parse_address(p, "pool_address"))]
pub pool_address: Option<Pubkey>,
/// The vote account corresponding to the pool to deposit into
#[clap(long = "vote-account", value_parser = |p: &str| parse_address(p, "vote_account_address"))]
pub vote_account_address: Option<Pubkey>,
/// The wallet to deposit lamports from. Defaults to the client
/// keypair
#[clap(long, id = "DEPOSIT_SOURCE_KEYPAIR", value_parser = SignerSourceParserBuilder::default().allow_all().build())]
pub from: Option<SignerSource>,
/// The token account to mint to. Defaults to the client keypair's
/// associated token account
#[clap(long = "token-account", value_parser = |p: &str| parse_address(p, "token_account_address"))]
pub token_account_address: Option<Pubkey>,
}
fn pool_source_group() -> ArgGroup<'static> {
ArgGroup::new("pool-source")
.required(true)
.args(&["pool-address", "vote-account-address"])
}
fn parse_address(path: &str, name: &str) -> Result<Pubkey, String> {
let mut wallet_manager = None;
pubkey_from_path(&ArgMatches::default(), path, name, &mut wallet_manager)
.map_err(|_| format!("Failed to load pubkey {} at {}", name, path))
}
pub fn parse_output_format(output_format: &str) -> OutputFormat {
match output_format {
"json" => OutputFormat::Json,
"json-compact" => OutputFormat::JsonCompact,
_ => unreachable!(),
}
}
pub fn is_valid_token_name(s: &str) -> Result<(), String> {
if s.len() > 32 {
Err("Maximum token name length is 32 characters".to_string())
} else {
Ok(())
}
}
pub fn is_valid_token_symbol(s: &str) -> Result<(), String> {
if s.len() > 10 {
Err("Maximum token symbol length is 10 characters".to_string())
} else {
Ok(())
}
}
pub fn is_valid_token_uri(s: &str) -> Result<(), String> {
if s.len() > 200 {
Err("Maximum token URI length is 200 characters".to_string())
} else {
Ok(())
}
}
pub fn pool_address_from_args(maybe_pool: Option<Pubkey>, maybe_vote: Option<Pubkey>) -> Pubkey {
if let Some(pool_address) = maybe_pool {
pool_address
} else if let Some(vote_account_address) = maybe_vote {
find_pool_address(&spl_single_pool::id(), &vote_account_address)
} else {
unreachable!()
}
}
#[cfg(test)]
mod tests {
// if this test fails, we changed the fee. fix the comment on Command::DepositSol
#[test]
fn test_deposit_sol_fee() {
assert_eq!(spl_single_pool::DEPOSIT_SOL_FEE_BPS, 100);
}
}