Skip to main content

sendfun_sdk/launchpad/
migrate.rs

1use crate::constants::{
2	ATA_PROGRAM_ID, DEX_PROGRAM_ID, LAUNCHPAD_PROGRAM_ID, NEXUS_PROGRAM_ID,
3	SYSTEM_PROGRAM_ID, TOKEN_2022_PROGRAM_ID,
4};
5use crate::transfer_hook::{HookLeg, HookLegs};
6use crate::utils::{
7	BuildError, MintInfo, TradeMints, append_hooks, check_base_mint,
8};
9use solana_address::Address;
10use solana_instruction::Instruction;
11
12#[derive(Clone, Debug)]
13pub struct BuildMigrateParams<'a> {
14	pub caller: &'a Address,
15	/// From [`MintInfo::load`].
16	pub base_mint_info: &'a MintInfo,
17	/// From [`MintInfo::load`].
18	pub quote_mint_info: &'a MintInfo,
19}
20
21/// Builds `migrate`. Simulate the transaction: a hook can refuse it.
22pub fn build_migrate_instruction(
23	p: &BuildMigrateParams<'_>,
24) -> Result<Instruction, BuildError> {
25	check_base_mint(p.base_mint_info)?;
26	let (instruction, legs) = bare_migrate_instruction(p);
27	append_hooks(
28		instruction,
29		TradeMints {
30			base: p.base_mint_info,
31			quote: p.quote_mint_info,
32		},
33		&legs,
34	)
35}
36
37/// The instruction and its two legs, quote first, as the program moves them.
38/// Each leg moves a curve vault into the pool vault that `migrate` opens,
39/// signed by the curve. The program writes no account that a list can read
40/// before the transfers.
41fn bare_migrate_instruction(
42	p: &BuildMigrateParams<'_>,
43) -> (Instruction, HookLegs) {
44	let base_mint = &p.base_mint_info.address;
45	let quote_mint = &p.quote_mint_info.address;
46	let quote_token_program = &p.quote_mint_info.token_program;
47
48	let (bonding_curve, _) =
49		super::pda::find_bonding_curve_pda(base_mint, quote_mint);
50	let (base_vault, _) = crate::utils::find_associated_token_pda(
51		&bonding_curve,
52		base_mint,
53		&TOKEN_2022_PROGRAM_ID,
54	);
55	let (quote_vault, _) = crate::utils::find_associated_token_pda(
56		&bonding_curve,
57		quote_mint,
58		quote_token_program,
59	);
60	let (creator_fee_config, _) =
61		super::pda::find_creator_fee_config_pda(base_mint, quote_mint);
62
63	let (pool, _) = crate::dex::pda::find_pool_pda(base_mint, quote_mint);
64	let (lp_mint, _) = crate::dex::pda::find_lp_mint_pda(base_mint, quote_mint);
65	let (pool_base_vault, _) = crate::utils::find_associated_token_pda(
66		&pool,
67		base_mint,
68		&TOKEN_2022_PROGRAM_ID,
69	);
70	let (pool_quote_vault, _) = crate::utils::find_associated_token_pda(
71		&pool,
72		quote_mint,
73		quote_token_program,
74	);
75	let (pool_lp_account, _) = crate::utils::find_associated_token_pda(
76		&pool,
77		&lp_mint,
78		&TOKEN_2022_PROGRAM_ID,
79	);
80	let (dex_creator_fee_config, _) =
81		crate::dex::pda::find_creator_fee_config_pda(base_mint, quote_mint);
82
83	let migrate = super::instructions::Migrate {
84		caller: *p.caller,
85		bonding_curve,
86		base_mint: *base_mint,
87		quote_mint: *quote_mint,
88		base_vault,
89		quote_vault,
90		dex_program: DEX_PROGRAM_ID,
91		migration_authority: super::pda::MIGRATION_AUTHORITY_ADDRESS,
92		pool,
93		lp_mint,
94		pool_base_vault,
95		pool_quote_vault,
96		pool_lp_account,
97		dex_event_authority: crate::dex::pda::EVENT_AUTHORITY_ADDRESS,
98		nexus_program: NEXUS_PROGRAM_ID,
99		creator_fee_config,
100		base_token_program: TOKEN_2022_PROGRAM_ID,
101		quote_token_program: *quote_token_program,
102		associated_token_program: ATA_PROGRAM_ID,
103		system_program: SYSTEM_PROGRAM_ID,
104		event_authority: super::pda::EVENT_AUTHORITY_ADDRESS,
105		program: LAUNCHPAD_PROGRAM_ID,
106		dex_creator_fee_config,
107	};
108	let from_curve = |source, mint, destination| HookLeg {
109		source,
110		mint,
111		destination,
112		authority: bonding_curve,
113		destination_owner: pool,
114	};
115	let legs = HookLegs {
116		legs: vec![
117			from_curve(quote_vault, *quote_mint, pool_quote_vault),
118			from_curve(base_vault, *base_mint, pool_base_vault),
119		],
120		written: Vec::new(),
121	};
122	(migrate.instruction(), legs)
123}
124
125#[cfg(test)]
126mod tests {
127	use solana_address::Address;
128
129	use super::{BuildMigrateParams, build_migrate_instruction};
130	use crate::constants::{
131		DEX_PROGRAM_ID, LAUNCHPAD_PROGRAM_ID, TOKEN_2022_PROGRAM_ID,
132		TOKEN_DECIMALS, WSOL_MINT, WSOL_MINT_INFO,
133	};
134	use crate::utils::MintInfo;
135
136	const CREATOR_FEE_CONFIG_INDEX: usize = 15;
137	const DEX_CREATOR_FEE_CONFIG_INDEX: usize = 22;
138
139	const BASE_MINT: Address = Address::new_from_array([2u8; 32]);
140
141	fn migrate() -> solana_instruction::Instruction {
142		let caller = Address::new_from_array([1u8; 32]);
143		build_migrate_instruction(&BuildMigrateParams {
144			caller: &caller,
145			base_mint_info: &MintInfo::plain(
146				BASE_MINT,
147				TOKEN_2022_PROGRAM_ID,
148				TOKEN_DECIMALS,
149			),
150			quote_mint_info: &WSOL_MINT_INFO,
151		})
152		.unwrap()
153	}
154
155	fn creator_fee_config_pda(program: &Address) -> Address {
156		Address::find_program_address(
157			&[
158				b"creator_fee_config",
159				BASE_MINT.as_ref(),
160				WSOL_MINT.as_ref(),
161			],
162			program,
163		)
164		.0
165	}
166
167	#[test]
168	fn the_creator_fee_config_slot_is_the_curves_pda() {
169		let ix = migrate();
170
171		assert_eq!(ix.accounts.len(), 23);
172		let slot = &ix.accounts[CREATOR_FEE_CONFIG_INDEX];
173		assert_eq!(slot.pubkey, creator_fee_config_pda(&LAUNCHPAD_PROGRAM_ID));
174		assert_eq!(
175			slot.pubkey,
176			solana_address::address!(
177				"BMrF9UWCQwk4bYq9nYsfLWFCVx6UhgLXrxJCABENvd19"
178			),
179		);
180		assert!(!slot.is_signer);
181		assert!(slot.is_writable);
182	}
183
184	#[test]
185	fn the_last_slot_is_the_pools_creator_fee_config_pda() {
186		let ix = migrate();
187
188		let slot = &ix.accounts[DEX_CREATOR_FEE_CONFIG_INDEX];
189		assert_eq!(slot.pubkey, creator_fee_config_pda(&DEX_PROGRAM_ID));
190		assert_eq!(
191			slot.pubkey,
192			solana_address::address!(
193				"DBtuuqp2KVdSJG6ffEd3NiLwhuBwybSXANx7YtjUsG6w"
194			),
195		);
196		assert!(!slot.is_signer);
197		assert!(slot.is_writable);
198	}
199
200	#[test]
201	fn the_curves_and_the_pools_slots_differ() {
202		let ix = migrate();
203
204		assert_ne!(
205			ix.accounts[CREATOR_FEE_CONFIG_INDEX].pubkey,
206			ix.accounts[DEX_CREATOR_FEE_CONFIG_INDEX].pubkey,
207		);
208	}
209}