Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: withdraw SPL token to new account (by creating it) #53

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,4 @@ brew install gnu-tar
# Put this in ~/.zshrc
export PATH="/usr/local/opt/gnu-tar/libexec/gnubin:$PATH"
```
see https://solana.stackexchange.com/questions/4499/blockstore-error-when-starting-solana-test-validator-on-macos-13-0-1/16319#16319
see https://solana.stackexchange.com/questions/4499/blockstore-error-when-starting-solana-test-validator-on-macos-13-0-1/16319#16319
2 changes: 1 addition & 1 deletion programs/protocol-contracts-solana/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ idl-build = ["anchor-lang/idl-build"]

[dependencies]
anchor-lang = { version = "=0.30.0" }
anchor-spl = { version = "=0.30.0" , features = ["idl-build"]}
anchor-spl = { version = "=0.30.0", features = ["idl-build"] }
anchor-syn = "=0.30.0"
spl-associated-token-account = "3.0.2"
solana-program = "=1.18.15"
97 changes: 91 additions & 6 deletions programs/protocol-contracts-solana/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use anchor_lang::prelude::*;
use anchor_lang::system_program;
use anchor_spl::associated_token::get_associated_token_address;
use anchor_spl::associated_token::{get_associated_token_address, AssociatedToken};
use anchor_spl::token::{transfer, transfer_checked, Mint, Token, TokenAccount};
use solana_program::keccak::hash;
use solana_program::program::invoke;
use solana_program::secp256k1_recover::secp256k1_recover;
use solana_program::sysvar::{rent::Rent, Sysvar};
use spl_associated_token_account::instruction::create_associated_token_account;
use std::mem::size_of;

#[error_code]
Expand Down Expand Up @@ -97,6 +100,10 @@ pub mod gateway {
Ok(())
}

pub fn initialize_rent_payer(_ctx: Context<InitializeRentPayer>) -> Result<()> {
Ok(())
}

// deposit SOL into this program and the `receiver` on ZetaChain zEVM
// will get corresponding ZRC20 credit.
// amount: amount of lamports (10^-9 SOL) to deposit
Expand Down Expand Up @@ -267,7 +274,7 @@ pub mod gateway {
concatenated_buffer.extend_from_slice(&nonce.to_be_bytes());
concatenated_buffer.extend_from_slice(&amount.to_be_bytes());
concatenated_buffer.extend_from_slice(&ctx.accounts.mint_account.key().to_bytes());
concatenated_buffer.extend_from_slice(&ctx.accounts.to.key().to_bytes());
concatenated_buffer.extend_from_slice(&ctx.accounts.recipient_ata.key().to_bytes());
require!(
message_hash == hash(&concatenated_buffer[..]).to_bytes(),
Errors::MessageHashMismatch
Expand All @@ -286,18 +293,77 @@ pub mod gateway {
let pda_ata = get_associated_token_address(&pda.key(), &ctx.accounts.mint_account.key());
require!(
pda_ata == ctx.accounts.pda_ata.to_account_info().key(),
Errors::SPLAtaAndMintAddressMismatch
Errors::SPLAtaAndMintAddressMismatch,
);

let token = &ctx.accounts.token_program;
let signer_seeds: &[&[&[u8]]] = &[&[b"meta", &[ctx.bumps.pda]]];
// make sure that ctx.accounts.recipient_ata is ATA (PDA account of token program)
let recipient_ata = get_associated_token_address(
&ctx.accounts.recipient.key(),
&ctx.accounts.mint_account.key(),
);
require!(
recipient_ata == ctx.accounts.recipient_ata.to_account_info().key(),
Errors::SPLAtaAndMintAddressMismatch,
);

// test whether the recipient_ata is created or not; if not, create it
let recipient_ata_account = ctx.accounts.recipient_ata.to_account_info();
if recipient_ata_account.lamports() == 0
|| *recipient_ata_account.owner == ctx.accounts.system_program.key()
{
// if lamports of recipient_ata_account is 0 or its owner being system program then it's not created
msg!(
"Creating associated token account {:?} for recipient {:?}...",
recipient_ata_account.key(),
ctx.accounts.recipient.key(),
);
let signer_info = &ctx.accounts.signer.to_account_info();
let bal0 = signer_info.lamports();
invoke(
&create_associated_token_account(
ctx.accounts.signer.to_account_info().key,
ctx.accounts.recipient.to_account_info().key,
ctx.accounts.mint_account.to_account_info().key,
ctx.accounts.token_program.key,
),
&[
ctx.accounts.mint_account.to_account_info().clone(),
ctx.accounts.recipient_ata.clone(),
ctx.accounts.recipient.to_account_info().clone(),
ctx.accounts.signer.to_account_info().clone(),
ctx.accounts.system_program.to_account_info().clone(),
ctx.accounts.token_program.to_account_info().clone(),
ctx.accounts
.associated_token_program
.to_account_info()
.clone(),
],
)?;
let bal1 = signer_info.lamports();

msg!("Associated token account for recipient created!");
msg!(
"Refunding the rent paid by the signer {:?}",
ctx.accounts.signer.to_account_info().key
);

let rent_payer_info = ctx.accounts.rent_payer_pda.to_account_info();
rent_payer_info.sub_lamports(bal0 - bal1)?;
signer_info.add_lamports(bal0 - bal1)?;
msg!(
"Signer refunded the ATA account creation rent amount {:?} lamports",
bal0 - bal1
);
}

let xfer_ctx = CpiContext::new_with_signer(
token.to_account_info(),
anchor_spl::token::TransferChecked {
from: ctx.accounts.pda_ata.to_account_info(),
mint: ctx.accounts.mint_account.to_account_info(),
to: ctx.accounts.to.to_account_info(),
to: ctx.accounts.recipient_ata.to_account_info(),
authority: pda.to_account_info(),
},
signer_seeds,
Expand Down Expand Up @@ -393,15 +459,22 @@ pub struct WithdrawSPLToken<'info> {
#[account(mut, seeds = [b"meta"], bump)]
pub pda: Account<'info, Pda>,

#[account(mut, token::mint = mint_account, token::authority = pda)]
#[account(mut, associated_token::mint = mint_account, associated_token::authority = pda)]
pub pda_ata: Account<'info, TokenAccount>, // associated token address of PDA

pub mint_account: Account<'info, Mint>,

pub recipient: SystemAccount<'info>,
/// CHECK: recipient_ata might not have been created; avoid checking its content.
/// the validation will be done in the instruction processor.
#[account(mut)]
pub to: Account<'info, TokenAccount>,
pub recipient_ata: AccountInfo<'info>,

#[account(mut, seeds = [b"rent-payer"], bump)]
pub rent_payer_pda: Account<'info, RentPayerPda>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
Expand Down Expand Up @@ -473,6 +546,15 @@ pub struct Unwhitelist<'info> {
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct InitializeRentPayer<'info> {
#[account(init, payer = authority, space = 8, seeds = [b"rent-payer"], bump)]
pub rent_payer_pda: Account<'info, RentPayerPda>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}

#[account]
pub struct Pda {
nonce: u64, // ensure that each signature can only be used once
Expand All @@ -485,6 +567,9 @@ pub struct Pda {
#[account]
pub struct WhitelistEntry {}

#[account]
pub struct RentPayerPda {}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading
Loading