Skip to content

Commit 31ac5a5

Browse files
abelmarnkjoncinquefebo
authored
Add unwrap_lamports instruction (#857)
* Add COption pack/unpack helpers * Add instruction builder * Add serde tests for new instruction using coption_u64 * Corrected instruction docs * Add PodTokenInstruction helpers * Add unwrap lamports processor * Add unwrap lamports cli clap_app parser * Add unwrap lamports cli handler * Add auto-generated js code * Add js COption serialization helper * Add unwrap lamports js action & instruction * Add unwrap lamports js e2e tests * Add unwrap lamports rust-legacy processor * Updated rust-legacy cpi guard helpers & tests * Added unwrap lamport rust-legacy tests * Remove spl-token-2022-interface patch * Remove memo code from unwrap lamports processor * Fix self transfer in unwrap lamports processor * Refactor self transfer in unwrap lamports processor * Fix help description for UnwrapLamports amount argument Co-authored-by: Jon C <[email protected]> * Fix None lamport amount for UnwrapLamports Co-authored-by: Jon C <[email protected]> * Remove CPI-guard delegate allowance check Co-authored-by: Fernando Otero <[email protected]> * Relax token program account check in instruction builder Co-authored-by: Jon C <[email protected]> * Racfactor and update processor for the self-transfer case * Add rust COptionU64 de-serialization helper * Refactor and extend rust legacy tests * Fix unwrap lamports js docs, imports & comments * Fix instruction data check in unwrap lamports js instruction deconder * Update js umwrap lamports test * Rename UnwrapLamports to UnwrapSol in cli parser * Add allow-unfunded-recipient flag * Update cli unwrap lamports test & add unwrap lamports multisig test * Allow for unfunded recipients in unwrap lamports cli processor & update display messages * Remove SOL transfer funding in cli unwrap lamports unfunded case * Update clients/cli/src/clap_app.rs Co-authored-by: Jon C <[email protected]> * Fix client generated docs * Aid compiler. Co-authored-by: Fernando Otero <[email protected]> * Remove unused variable Co-authored-by: Fernando Otero <[email protected]> * Renaming changes --------- Co-authored-by: Jon C <[email protected]> Co-authored-by: Fernando Otero <[email protected]>
1 parent 7e76765 commit 31ac5a5

File tree

23 files changed

+2736
-24
lines changed

23 files changed

+2736
-24
lines changed

Cargo.lock

Lines changed: 35 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,3 @@ consolidate-commits = false
6262
[patch.crates-io]
6363
spl-token-confidential-transfer-proof-extraction = { path = "confidential/proof-extraction" }
6464
spl-token-confidential-transfer-proof-generation = { path = "confidential/proof-generation" }
65-
spl-token-2022-interface = { path = "interface" }

clients/cli/src/clap_app.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ pub enum CommandName {
172172
UpdateUiAmountMultiplier,
173173
Pause,
174174
Resume,
175+
UnwrapSol,
175176
}
176177
impl fmt::Display for CommandName {
177178
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -1721,6 +1722,53 @@ pub fn app<'a>(
17211722
.nonce_args(true)
17221723
.offline_args(),
17231724
)
1725+
.subcommand(
1726+
SubCommand::with_name(CommandName::UnwrapSol.into())
1727+
.about("Unwrap SOL from a wrapped SOL token account")
1728+
.arg(
1729+
Arg::with_name("amount")
1730+
.value_parser(Amount::parse)
1731+
.value_name("TOKEN_AMOUNT")
1732+
.takes_value(true)
1733+
.index(1)
1734+
.required(true)
1735+
.help("Amount to unwrap, in SOL; accepts keyword ALL"),
1736+
)
1737+
.arg(
1738+
Arg::with_name("recipient")
1739+
.validator(|s| is_valid_pubkey(s))
1740+
.value_name("RECIPIENT_ACCOUNT_ADDRESS")
1741+
.takes_value(true)
1742+
.index(2)
1743+
.help("Specify the address to recieve the unwrapped SOL. \
1744+
Defaults to the owner address.")
1745+
)
1746+
.arg(
1747+
Arg::with_name("from")
1748+
.validator(|s| is_valid_pubkey(s))
1749+
.value_name("NATIVE_TOKEN_ACCOUNT_ADDRESS")
1750+
.takes_value(true)
1751+
.long("from")
1752+
.help("Specify the token account that contains the wrapped SOL. \
1753+
[default: owner's associated token account]")
1754+
)
1755+
.arg(owner_keypair_arg_with_value_name("NATIVE_TOKEN_OWNER_KEYPAIR")
1756+
.help(
1757+
"Specify the keypair for the wallet which owns the wrapped SOL. \
1758+
This may be a keypair file or the ASK keyword. \
1759+
Defaults to the client keypair.",
1760+
),
1761+
)
1762+
.arg(
1763+
Arg::with_name("allow_unfunded_recipient")
1764+
.long("allow-unfunded-recipient")
1765+
.takes_value(false)
1766+
.help("Complete the transfer even if the recipient address is not funded")
1767+
)
1768+
.arg(multisig_signer_arg())
1769+
.nonce_args(true)
1770+
.offline_args(),
1771+
)
17241772
.subcommand(
17251773
SubCommand::with_name(CommandName::Approve.into())
17261774
.about("Approve a delegate for a token account")

clients/cli/src/command.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2099,6 +2099,103 @@ async fn command_unwrap(
20992099
})
21002100
}
21012101

2102+
async fn command_unwrap_sol(
2103+
config: &Config<'_>,
2104+
ui_amount: Amount,
2105+
source_owner: Pubkey,
2106+
source_account: Option<Pubkey>,
2107+
destination_account: Option<Pubkey>,
2108+
allow_unfunded_recipient: bool,
2109+
bulk_signers: BulkSigners,
2110+
) -> CommandResult {
2111+
let use_associated_account = source_account.is_none();
2112+
let token = native_token_client_from_config(config)?;
2113+
2114+
let source_account =
2115+
source_account.unwrap_or_else(|| token.get_associated_token_address(&source_owner));
2116+
2117+
let destination_account = destination_account.unwrap_or(source_owner);
2118+
2119+
let amount = match ui_amount.sol_to_lamport() {
2120+
Amount::Raw(ui_amount) => Some(ui_amount),
2121+
Amount::Decimal(_) => unreachable!(),
2122+
Amount::All => None,
2123+
};
2124+
let mut balance = None;
2125+
2126+
if !config.sign_only {
2127+
let account_data = config.get_account_checked(&source_account).await?;
2128+
2129+
if account_data.lamports == 0 {
2130+
if use_associated_account {
2131+
return Err("No wrapped SOL in associated account; did you mean to specify an auxiliary address?".to_string().into());
2132+
} else {
2133+
return Err(format!("No wrapped SOL in {}", source_account).into());
2134+
}
2135+
}
2136+
2137+
let account_state = StateWithExtensionsOwned::<Account>::unpack(account_data.data)?;
2138+
2139+
if let Some(amount) = amount {
2140+
if account_state.base.amount < amount {
2141+
return Err(format!(
2142+
"Error: Sender has insufficient funds, current balance is {} SOL",
2143+
build_balance_message(account_state.base.amount, false, false)
2144+
)
2145+
.into());
2146+
}
2147+
}
2148+
2149+
balance = Some(account_state.base.amount);
2150+
2151+
if !use_associated_account && account_state.base.mint != *token.get_address() {
2152+
return Err(format!("{} is not a native token account", source_account).into());
2153+
}
2154+
2155+
if config.rpc_client.get_balance(&destination_account).await? == 0 {
2156+
// if it doesn't exist, we gate transfer with a different flag
2157+
if !allow_unfunded_recipient {
2158+
return Err("Error: The recipient address is not funded. \
2159+
Add `--allow-unfunded-recipient` to complete the transfer."
2160+
.into());
2161+
}
2162+
}
2163+
}
2164+
2165+
let display_amount = amount
2166+
.or(balance)
2167+
.map(|amount| build_balance_message(amount, false, false))
2168+
.unwrap_or_else(|| "all".to_string());
2169+
2170+
println_display(
2171+
config,
2172+
format!(
2173+
"Unwrapping {} SOL to {}",
2174+
display_amount, destination_account
2175+
),
2176+
);
2177+
2178+
let res = token
2179+
.unwrap_lamports(
2180+
&source_account,
2181+
&destination_account,
2182+
&source_owner,
2183+
amount,
2184+
&bulk_signers,
2185+
)
2186+
.await?;
2187+
2188+
let tx_return = finish_tx(config, &res, false).await?;
2189+
Ok(match tx_return {
2190+
TransactionReturnData::CliSignature(signature) => {
2191+
config.output_format.formatted_string(&signature)
2192+
}
2193+
TransactionReturnData::CliSignOnlyData(sign_only_data) => {
2194+
config.output_format.formatted_string(&sign_only_data)
2195+
}
2196+
})
2197+
}
2198+
21022199
#[allow(clippy::too_many_arguments)]
21032200
async fn command_approve(
21042201
config: &Config<'_>,
@@ -4273,6 +4370,31 @@ pub async fn process_command(
42734370
let account = pubkey_of_signer(arg_matches, "account", &mut wallet_manager).unwrap();
42744371
command_unwrap(config, wallet_address, account, bulk_signers).await
42754372
}
4373+
(CommandName::UnwrapSol, arg_matches) => {
4374+
let (owner_signer, owner) =
4375+
config.signer_or_default(arg_matches, "owner", &mut wallet_manager);
4376+
if config.multisigner_pubkeys.is_empty() {
4377+
push_signer_with_dedup(owner_signer, &mut bulk_signers);
4378+
}
4379+
4380+
let amount = *arg_matches.get_one::<Amount>("amount").unwrap();
4381+
let source = pubkey_of_signer(arg_matches, "from", &mut wallet_manager).unwrap();
4382+
let recipient =
4383+
pubkey_of_signer(arg_matches, "recipient", &mut wallet_manager).unwrap();
4384+
4385+
let allow_unfunded_recipient = arg_matches.is_present("allow_unfunded_recipient");
4386+
4387+
command_unwrap_sol(
4388+
config,
4389+
amount,
4390+
owner,
4391+
source,
4392+
recipient,
4393+
allow_unfunded_recipient,
4394+
bulk_signers,
4395+
)
4396+
.await
4397+
}
42764398
(CommandName::Approve, arg_matches) => {
42774399
let (owner_signer, owner_address) =
42784400
config.signer_or_default(arg_matches, "owner", &mut wallet_manager);

0 commit comments

Comments
 (0)