Skip to content

Commit f816a9e

Browse files
authored
feat: add withdraw spl fees ix (#584)
* add withdraw spl fees ix * try * locked anchor install * correct verifiable build link * fix
1 parent 3d8cb04 commit f816a9e

10 files changed

Lines changed: 240 additions & 7 deletions

File tree

.github/workflows/ci-pre-commit.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242
sudo apt-get install -y libudev-dev pkg-config
4343
- name: Install Anchor CLI
4444
run: |
45-
cargo install --git https://github.com/coral-xyz/anchor avm
45+
cargo install --locked --git https://github.com/coral-xyz/anchor avm
4646
avm install 0.31.0
4747
avm use 0.31.0
4848
echo "$HOME/.avm/bin" >> $GITHUB_PATH

.github/workflows/test-and-release-svm-contract.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
- uses: actions/checkout@v2
2424
- name: Install Solana Verify CLI
2525
run: |
26-
cargo install solana-verify --git https://github.com/Ellipsis-Labs/solana-verifiable-build --rev 5ff03e0
26+
cargo install --locked solana-verify --git https://github.com/solana-foundation/solana-verifiable-build --rev 5ff03e0
2727
- name: Build
2828
run: solana-verify build
2929
- name: Run tests

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contracts/svm/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contracts/svm/programs/express_relay/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "express-relay"
3-
version = "0.8.0"
3+
version = "0.9.0"
44
description = "Pyth Express Relay program for handling permissioning and bid distribution"
55
repository = "https://github.com/pyth-network/per"
66
license = "Apache-2.0"

contracts/svm/programs/express_relay/src/lib.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ use {
2929
system_program::System,
3030
},
3131
anchor_spl::token_interface::{
32+
transfer_checked,
3233
Mint,
3334
TokenAccount,
3435
TokenInterface,
36+
TransferChecked,
3537
},
3638
};
3739

@@ -182,6 +184,35 @@ pub mod express_relay {
182184
)
183185
}
184186

187+
pub fn withdraw_spl_fees(ctx: Context<WithdrawSplFees>) -> Result<()> {
188+
let amount = ctx.accounts.express_relay_fee_receiver_ata.amount;
189+
if amount == 0 {
190+
return Ok(());
191+
}
192+
193+
let metadata_bump = ctx.bumps.express_relay_metadata;
194+
let signer_seeds: &[&[u8]] = &[SEED_METADATA, &[metadata_bump]];
195+
let signer = &[signer_seeds];
196+
let cpi_accounts = TransferChecked {
197+
from: ctx
198+
.accounts
199+
.express_relay_fee_receiver_ata
200+
.to_account_info(),
201+
to: ctx.accounts.fee_receiver_admin_ta.to_account_info(),
202+
mint: ctx.accounts.mint_fee.to_account_info(),
203+
authority: ctx.accounts.express_relay_metadata.to_account_info(),
204+
};
205+
transfer_checked(
206+
CpiContext::new_with_signer(
207+
ctx.accounts.token_program_fee.to_account_info(),
208+
cpi_accounts,
209+
signer,
210+
),
211+
amount,
212+
ctx.accounts.mint_fee.decimals,
213+
)
214+
}
215+
185216
pub fn swap_internal(ctx: Context<Swap>, data: SwapV2Args) -> Result<()> {
186217
ctx.accounts.check_raw_constraints(data.fee_token)?;
187218
check_deadline(data.deadline)?;
@@ -224,7 +255,6 @@ pub mod express_relay {
224255
amount_user_after_fees,
225256
)?;
226257

227-
228258
Ok(())
229259
}
230260

@@ -412,6 +442,35 @@ pub struct WithdrawFees<'info> {
412442
pub express_relay_metadata: Account<'info, ExpressRelayMetadata>,
413443
}
414444

445+
#[derive(Accounts)]
446+
pub struct WithdrawSplFees<'info> {
447+
pub admin: Signer<'info>,
448+
449+
#[account(mut, seeds = [SEED_METADATA], bump, has_one = admin)]
450+
pub express_relay_metadata: Account<'info, ExpressRelayMetadata>,
451+
452+
#[account(
453+
mut,
454+
associated_token::mint = mint_fee,
455+
associated_token::authority = express_relay_metadata,
456+
associated_token::token_program = token_program_fee,
457+
)]
458+
pub express_relay_fee_receiver_ata: Box<InterfaceAccount<'info, TokenAccount>>,
459+
460+
/// this can just be any token account for this mint
461+
#[account(
462+
mut,
463+
token::mint = mint_fee,
464+
token::token_program = token_program_fee,
465+
)]
466+
pub fee_receiver_admin_ta: Box<InterfaceAccount<'info, TokenAccount>>,
467+
468+
#[account(mint::token_program = token_program_fee)]
469+
pub mint_fee: Box<InterfaceAccount<'info, Mint>>,
470+
471+
pub token_program_fee: Interface<'info, TokenInterface>,
472+
}
473+
415474
#[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Copy, Debug)]
416475
pub enum FeeToken {
417476
Searcher,

contracts/svm/testing/src/express_relay/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ pub mod set_swap_platform_fee;
99
pub mod submit_bid;
1010
pub mod swap;
1111
pub mod withdraw_fees;
12+
pub mod withdraw_spl_fees;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use {
2+
super::helpers::get_express_relay_metadata_key,
3+
anchor_lang::{
4+
InstructionData,
5+
ToAccountMetas,
6+
},
7+
express_relay::accounts::WithdrawSplFees,
8+
solana_sdk::{
9+
instruction::Instruction,
10+
pubkey::Pubkey,
11+
signature::Keypair,
12+
signer::Signer,
13+
},
14+
};
15+
16+
pub fn withdraw_spl_fees_instruction(
17+
admin: &Keypair,
18+
express_relay_fee_receiver_ata: Pubkey,
19+
fee_receiver_admin_ta: Pubkey,
20+
mint_fee: Pubkey,
21+
token_program_fee: Pubkey,
22+
) -> Instruction {
23+
let express_relay_metadata = get_express_relay_metadata_key();
24+
25+
Instruction {
26+
program_id: express_relay::id(),
27+
data: express_relay::instruction::WithdrawSplFees {}.data(),
28+
accounts: WithdrawSplFees {
29+
admin: admin.pubkey(),
30+
express_relay_metadata,
31+
express_relay_fee_receiver_ata,
32+
fee_receiver_admin_ta,
33+
mint_fee,
34+
token_program_fee,
35+
}
36+
.to_account_metas(None),
37+
}
38+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
use {
2+
anchor_lang::error::ErrorCode as AnchorErrorCode,
3+
anchor_spl::{
4+
associated_token::{
5+
get_associated_token_address_with_program_id,
6+
spl_associated_token_account::instruction::create_associated_token_account_idempotent,
7+
},
8+
token::spl_token,
9+
},
10+
solana_sdk::{
11+
instruction::InstructionError,
12+
signature::Keypair,
13+
signer::Signer,
14+
},
15+
testing::{
16+
express_relay::{
17+
helpers::get_express_relay_metadata_key,
18+
withdraw_spl_fees::withdraw_spl_fees_instruction,
19+
},
20+
helpers::{
21+
assert_custom_error,
22+
generate_and_fund_key,
23+
submit_transaction,
24+
},
25+
setup::{
26+
setup,
27+
SetupResult,
28+
},
29+
token::Token,
30+
},
31+
};
32+
33+
#[test]
34+
fn test_withdraw_spl_fees() {
35+
let SetupResult { mut svm, admin, .. } = setup(None).expect("setup failed");
36+
37+
let express_relay_metadata = get_express_relay_metadata_key();
38+
let fee_receiver_admin = generate_and_fund_key(&mut svm);
39+
let fee_token = Token::create_mint(&mut svm, spl_token::ID, 6);
40+
41+
let express_relay_fee_receiver_ata = get_associated_token_address_with_program_id(
42+
&express_relay_metadata,
43+
&fee_token.mint,
44+
&fee_token.token_program,
45+
);
46+
let fee_receiver_admin_ata = get_associated_token_address_with_program_id(
47+
&fee_receiver_admin.pubkey(),
48+
&fee_token.mint,
49+
&fee_token.token_program,
50+
);
51+
52+
fee_token.airdrop(&mut svm, &express_relay_metadata, 3.5);
53+
54+
let create_admin_ata_ix = create_associated_token_account_idempotent(
55+
&admin.pubkey(),
56+
&fee_receiver_admin.pubkey(),
57+
&fee_token.mint,
58+
&fee_token.token_program,
59+
);
60+
let withdraw_ix = withdraw_spl_fees_instruction(
61+
&admin,
62+
express_relay_fee_receiver_ata,
63+
fee_receiver_admin_ata,
64+
fee_token.mint,
65+
fee_token.token_program,
66+
);
67+
submit_transaction(
68+
&mut svm,
69+
&[create_admin_ata_ix, withdraw_ix],
70+
&admin,
71+
&[&admin],
72+
)
73+
.unwrap();
74+
75+
assert!(Token::token_balance_matches(
76+
&mut svm,
77+
&express_relay_fee_receiver_ata,
78+
0,
79+
));
80+
assert!(Token::token_balance_matches(
81+
&mut svm,
82+
&fee_receiver_admin_ata,
83+
fee_token.get_amount_with_decimals(3.5),
84+
));
85+
}
86+
87+
#[test]
88+
fn test_withdraw_spl_fees_fail_wrong_admin() {
89+
let SetupResult { mut svm, .. } = setup(None).expect("setup failed");
90+
let wrong_admin = generate_and_fund_key(&mut svm);
91+
92+
let express_relay_metadata = get_express_relay_metadata_key();
93+
let fee_receiver_admin = Keypair::new();
94+
let fee_token = Token::create_mint(&mut svm, spl_token::ID, 6);
95+
let express_relay_fee_receiver_ata = get_associated_token_address_with_program_id(
96+
&express_relay_metadata,
97+
&fee_token.mint,
98+
&fee_token.token_program,
99+
);
100+
let fee_receiver_admin_ata = get_associated_token_address_with_program_id(
101+
&fee_receiver_admin.pubkey(),
102+
&fee_token.mint,
103+
&fee_token.token_program,
104+
);
105+
fee_token.airdrop(&mut svm, &express_relay_metadata, 1.0);
106+
let create_admin_ata_ix = create_associated_token_account_idempotent(
107+
&wrong_admin.pubkey(),
108+
&fee_receiver_admin.pubkey(),
109+
&fee_token.mint,
110+
&fee_token.token_program,
111+
);
112+
submit_transaction(
113+
&mut svm,
114+
&[create_admin_ata_ix],
115+
&wrong_admin,
116+
&[&wrong_admin],
117+
)
118+
.unwrap();
119+
120+
let withdraw_ix = withdraw_spl_fees_instruction(
121+
&wrong_admin,
122+
express_relay_fee_receiver_ata,
123+
fee_receiver_admin_ata,
124+
fee_token.mint,
125+
fee_token.token_program,
126+
);
127+
let tx_result = submit_transaction(&mut svm, &[withdraw_ix], &wrong_admin, &[&wrong_admin])
128+
.expect_err("Transaction should have failed");
129+
130+
assert_custom_error(
131+
tx_result.err,
132+
0,
133+
InstructionError::Custom(AnchorErrorCode::ConstraintHasOne.into()),
134+
);
135+
}

sdk/rust/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ solana-rpc-client = { workspace = true }
2121
borsh = { workspace = true }
2222
spl-associated-token-account = { workspace = true }
2323
spl-token = { workspace = true }
24-
express-relay = { version = "0.8.0", path = "../../contracts/svm/programs/express_relay" }
24+
express-relay = { version = "0.9.0", path = "../../contracts/svm/programs/express_relay" }
2525
spl-memo-client = { workspace = true }

0 commit comments

Comments
 (0)