初始化
initialize 指令执行两个主要任务:
初始化
Config账户,并存储 AMM 正常运行所需的所有信息。创建
mint_lp铸币账户,并将mint_authority分配给config账户。
所需账户
以下是此上下文所需的账户:
initializer:config账户的创建者。这不一定也必须是其权限持有者。必须是signer和mutable,因为此账户将支付config和mint_lp的初始化费用。mint_lp:代表池流动性的铸币账户。mint_authority应设置为config账户。必须作为mutable传递。config:正在初始化的配置账户。必须是mutable。system和token程序:初始化上述账户所需的程序账户。必须是executable。
由于与我们通常创建的结构体相比没有太多变化,我将把实现部分留给你:
pub struct InitializeAccounts<'a> {
pub initializer: &'a AccountInfo,
pub mint_lp: &'a AccountInfo,
pub config: &'a AccountInfo,
}
impl<'a> TryFrom<&'a [AccountInfo]> for InitializeAccounts<'a> {
type Error = ProgramError;
fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
//..
}
}指令数据
以下是我们需要传入的指令数据:
seed:用于PDA(程序派生地址)种子推导的随机数。这允许创建唯一的池实例。必须是一个[u64]fee:以基点表示的交换费(1基点=0.01%)。此费用在每次交易中收取,并分配给流动性提供者。必须是一个[u16]mint_x:池中代币X的SPL代币铸造地址。必须是一个[u8; 32]mint_y:池中代币Y的SPL代币铸造地址。必须是一个[u8; 32]config_bump:用于推导config账户PDA的bump种子。必须是一个u8lp_bump:用于推导lp_mint账户PDA的bump种子。必须是一个u8authority:将拥有AMM管理权限的公钥。如果未提供,池可以设置为不可变。必须是一个[u8; 32]
在此实现中,我们以比通常更灵活和底层的方式处理指令数据解析。因此,我们将解释为什么做出以下决策:
#[repr(C, packed)]
pub struct InitializeInstructionData {
pub seed: u64,
pub fee: u16,
pub mint_x: [u8; 32],
pub mint_y: [u8; 32],
pub config_bump: [u8; 1],
pub lp_bump: [u8; 1],
pub authority: [u8; 32],
}
impl TryFrom<&[u8]> for InitializeInstructionData {
type Error = ProgramError;
fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
const INITIALIZE_DATA_LEN_WITH_AUTHORITY: usize = size_of::<InitializeInstructionData>();
const INITIALIZE_DATA_LEN: usize =
INITIALIZE_DATA_LEN_WITH_AUTHORITY - size_of::<[u8; 32]>();
match data.len() {
INITIALIZE_DATA_LEN_WITH_AUTHORITY => {
Ok(unsafe { (data.as_ptr() as *const Self).read_unaligned() })
}
INITIALIZE_DATA_LEN => {
// If the authority is not present, we need to build the buffer and add it at the end before transmuting to the struct
let mut raw: MaybeUninit<[u8; INITIALIZE_DATA_LEN_WITH_AUTHORITY]> = MaybeUninit::uninit();
let raw_ptr = raw.as_mut_ptr() as *mut u8;
unsafe {
// Copy the provided data
core::ptr::copy_nonoverlapping(data.as_ptr(), raw_ptr, INITIALIZE_DATA_LEN);
// Add the authority to the end of the buffer
core::ptr::write_bytes(raw_ptr.add(INITIALIZE_DATA_LEN), 0, 32);
// Now transmute to the struct
Ok((raw.as_ptr() as *const Self).read_unaligned())
}
}
_ => Err(ProgramError::InvalidInstructionData),
}
}
}InitializeInstructionData 中的 authority 字段是可选的,可以省略以创建不可变池。
为了实现这一点并在创建不可变池时节省 32 字节的交易数据,我们会检查指令数据的长度并相应地解析数据;如果数据较短,我们将 authority 字段设置为 None,通过向缓冲区末尾写入 32 个零字节;如果包含完整的 authority 字段,我们会将字节切片直接转换为结构体。
指令逻辑
我们首先反序列化 instruction_data 和 accounts。
然后我们需要:
使用系统程序中的
CreateAccount指令和以下种子创建Config账户:
let seed_binding = self.instruction_data.seed.to_le_bytes();
let config_seeds = [
Seed::from(b"config"),
Seed::from(&seed_binding),
Seed::from(&self.instruction_data.mint_x),
Seed::from(&self.instruction_data.mint_y),
Seed::from(&self.instruction_data.config_bump),
];使用
Config::load_mut_unchecked()辅助工具加载Config账户并填充所需的所有数据,然后使用config.set_inner()辅助工具填充。使用
CreateAccount和InitializeMint2指令以及以下种子为 lp 创建Mint账户:
let mint_lp_seeds = [
Seed::from(b"mint_lp"),
Seed::from(self.accounts.config.key()),
Seed::from(&self.instruction_data.lp_bump),
];你应该足够熟练,可以自行完成这一部分,因此我将实现留给你:
pub struct Initialize<'a> {
pub accounts: InitializeAccounts<'a>,
pub instruction_data: InitializeInstructionData,
}
impl<'a> TryFrom<(&'a [u8], &'a [AccountInfo])> for Initialize<'a> {
type Error = ProgramError;
fn try_from((data, accounts): (&'a [u8], &'a [AccountInfo])) -> Result<Self, Self::Error> {
let accounts = InitializeAccounts::try_from(accounts)?;
let instruction_data: InitializeInstructionData = InitializeInstructionData::try_from(data)?;
Ok(Self {
accounts,
instruction_data,
})
}
}
impl<'a> Initialize<'a> {
pub const DISCRIMINATOR: &'a u8 = &0;
pub fn process(&mut self) -> ProgramResult {
//..
Ok(())
}
}安全性
如前所述,这可能看起来不寻常,但我们不需要对传入的账户进行显式检查。
这是因为在实际操作中,如果有问题,指令会失败;要么在 CPI(跨程序调用)期间,要么通过我们在程序中设置的早期检查失败。
例如,考虑 initializer 账户。我们期望它既是 signer 又是 mutable,但如果不是,CreateAccount 指令将会自动失败,因为它需要这些属性来满足 payer 的要求。
同样地,如果传递的 config 账户具有无效的 mint_x 或 mint_y,任何尝试向协议中存入资金的操作都会在代币转移期间失败。
随着经验的积累,您会发现可以省略许多检查,以保持指令的轻量化和优化,依赖系统和下游指令来强制执行约束。