Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 974541c

Browse files
authored
stake-pool: Update fee changes only once per epoch (#2269)
* fix * test for withdrawal fee * merge conflict changes
1 parent 7d748fc commit 974541c

File tree

2 files changed

+121
-8
lines changed

2 files changed

+121
-8
lines changed

stake-pool/program/src/processor.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1651,16 +1651,18 @@ impl Processor {
16511651
.ok_or(StakePoolError::CalculationFailure)?;
16521652
}
16531653

1654-
if let Some(next_epoch_fee) = stake_pool.next_epoch_fee {
1655-
stake_pool.fee = next_epoch_fee;
1656-
stake_pool.next_epoch_fee = None;
1657-
}
1658-
if let Some(next_withdrawal_fee) = stake_pool.next_withdrawal_fee {
1659-
stake_pool.withdrawal_fee = next_withdrawal_fee;
1660-
stake_pool.next_withdrawal_fee = None;
1654+
if stake_pool.last_update_epoch < clock.epoch {
1655+
if let Some(next_epoch_fee) = stake_pool.next_epoch_fee {
1656+
stake_pool.fee = next_epoch_fee;
1657+
stake_pool.next_epoch_fee = None;
1658+
}
1659+
if let Some(next_withdrawal_fee) = stake_pool.next_withdrawal_fee {
1660+
stake_pool.withdrawal_fee = next_withdrawal_fee;
1661+
stake_pool.next_withdrawal_fee = None;
1662+
}
1663+
stake_pool.last_update_epoch = clock.epoch;
16611664
}
16621665
stake_pool.total_stake_lamports = total_stake_lamports;
1663-
stake_pool.last_update_epoch = clock.epoch;
16641666

16651667
let pool_mint = Mint::unpack_from_slice(&pool_mint_info.data.borrow())?;
16661668
stake_pool.pool_token_supply = pool_mint.supply;

stake-pool/program/tests/set_withdrawal_fee.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,117 @@ async fn success() {
105105
assert_eq!(stake_pool.next_withdrawal_fee, None);
106106
}
107107

108+
#[tokio::test]
109+
async fn success_fee_cannot_increase_more_than_once() {
110+
let (mut context, stake_pool_accounts, new_withdrawal_fee) = setup(None).await;
111+
112+
let stake_pool = get_account(
113+
&mut context.banks_client,
114+
&stake_pool_accounts.stake_pool.pubkey(),
115+
)
116+
.await;
117+
let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool.data.as_slice()).unwrap();
118+
let old_withdrawal_fee = stake_pool.withdrawal_fee;
119+
120+
let transaction = Transaction::new_signed_with_payer(
121+
&[instruction::set_fee(
122+
&id(),
123+
&stake_pool_accounts.stake_pool.pubkey(),
124+
&stake_pool_accounts.manager.pubkey(),
125+
FeeType::Withdrawal(new_withdrawal_fee),
126+
)],
127+
Some(&context.payer.pubkey()),
128+
&[&context.payer, &stake_pool_accounts.manager],
129+
context.last_blockhash,
130+
);
131+
context
132+
.banks_client
133+
.process_transaction(transaction)
134+
.await
135+
.unwrap();
136+
137+
let stake_pool = get_account(
138+
&mut context.banks_client,
139+
&stake_pool_accounts.stake_pool.pubkey(),
140+
)
141+
.await;
142+
let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool.data.as_slice()).unwrap();
143+
144+
assert_eq!(stake_pool.withdrawal_fee, old_withdrawal_fee);
145+
assert_eq!(stake_pool.next_withdrawal_fee, Some(new_withdrawal_fee));
146+
147+
let first_normal_slot = context.genesis_config().epoch_schedule.first_normal_slot;
148+
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
149+
150+
context
151+
.warp_to_slot(first_normal_slot + slots_per_epoch)
152+
.unwrap();
153+
stake_pool_accounts
154+
.update_all(
155+
&mut context.banks_client,
156+
&context.payer,
157+
&context.last_blockhash,
158+
&[],
159+
false,
160+
)
161+
.await;
162+
163+
let stake_pool = get_account(
164+
&mut context.banks_client,
165+
&stake_pool_accounts.stake_pool.pubkey(),
166+
)
167+
.await;
168+
let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool.data.as_slice()).unwrap();
169+
assert_eq!(stake_pool.withdrawal_fee, new_withdrawal_fee);
170+
assert_eq!(stake_pool.next_withdrawal_fee, None);
171+
172+
// try setting to the old fee in the same epoch
173+
let transaction = Transaction::new_signed_with_payer(
174+
&[instruction::set_fee(
175+
&id(),
176+
&stake_pool_accounts.stake_pool.pubkey(),
177+
&stake_pool_accounts.manager.pubkey(),
178+
FeeType::Withdrawal(old_withdrawal_fee),
179+
)],
180+
Some(&context.payer.pubkey()),
181+
&[&context.payer, &stake_pool_accounts.manager],
182+
context.last_blockhash,
183+
);
184+
context
185+
.banks_client
186+
.process_transaction(transaction)
187+
.await
188+
.unwrap();
189+
190+
let stake_pool = get_account(
191+
&mut context.banks_client,
192+
&stake_pool_accounts.stake_pool.pubkey(),
193+
)
194+
.await;
195+
let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool.data.as_slice()).unwrap();
196+
assert_eq!(stake_pool.withdrawal_fee, new_withdrawal_fee);
197+
assert_eq!(stake_pool.next_withdrawal_fee, Some(old_withdrawal_fee));
198+
199+
let error = stake_pool_accounts
200+
.update_stake_pool_balance(
201+
&mut context.banks_client,
202+
&context.payer,
203+
&context.last_blockhash,
204+
)
205+
.await;
206+
assert!(error.is_none());
207+
208+
// Check that nothing has changed after updating the stake pool
209+
let stake_pool = get_account(
210+
&mut context.banks_client,
211+
&stake_pool_accounts.stake_pool.pubkey(),
212+
)
213+
.await;
214+
let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool.data.as_slice()).unwrap();
215+
assert_eq!(stake_pool.withdrawal_fee, new_withdrawal_fee);
216+
assert_eq!(stake_pool.next_withdrawal_fee, Some(old_withdrawal_fee));
217+
}
218+
108219
#[tokio::test]
109220
async fn success_increase_fee_from_0() {
110221
let (mut context, stake_pool_accounts, _) = setup(Some(Fee {

0 commit comments

Comments
 (0)