I'm trying to write a ICO contract with Anchor, which supposed to transfer SOL from user to program PDA account, and receives corresponding ico token.
Now I'm facing strange behaviour that initializing the ICO succeeds with depositing ICO token to the program PDA ico token account, but fails with InvaildProgramId error, if I pass PDA account to initialize SOL pool account for program.
the test code
it('Initializes the ICO', async () => {
console.log("---Initializes the presale---\n")
let bumps = new PoolBumps();
const protocol_ico_amount = 15000
const protocol_ico_amount_lamports = new BN(10 ** ico_token_mint_decimals * protocol_ico_amount)
const token_per_sol = new BN(100)
// Fetch the PDA of ico info account
const [ico_info_pda, ico_info_bump] = await PublicKey.findProgramAddressSync(
[Buffer.from(icoName)],
program.programId
);
const [ico_state_pda, ico_state_bump] = await PublicKey.findProgramAddressSync(
[Buffer.from("ico_state")],
program.programId
);
const [protocol_ico_token_pda, protocol_ico_token_bump] = await PublicKey.findProgramAddressSync(
[Buffer.from("protocol_ico_token")],
program.programId
);
const [protocol_sol_pool_pda, protocol_sol_pool_bump] = await PublicKey.findProgramAddressSync(
[Buffer.from("protocol_sol_pool")],
program.programId
);
await program.methods
.initialize(
protocol_ico_amount_lamports,
token_per_sol
)
.accounts({
icoInfoPda: ico_info_pda,
icoStatePda: ico_state_pda,
authority: admin.publicKey,
adminIcoTokenAccount: admin_ico_token_account.address,
protocolIcoTokenPda: protocol_ico_token_pda,
// protocolSolPoolPda: protocol_sol_pool_pda,
icoTokenMint: ico_token_mint,
systemProgram: SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID
})
.rpc();
const protocol_ico_account_balance_info = await provider.connection.getTokenAccountBalance(protocol_ico_token_pda)
assert.equal(protocol_ico_account_balance_info.value.amount.toString(), protocol_ico_amount_lamports.toNumber().toString(), "Ico token should be transferred to protocol token account");
const ico_info = await program.account.icoInfo.fetch(ico_info_pda);
console.log('ICO Authority: ', ico_info.authority.toString());
// console.log('ICO Protocol Wallet: ', ico_info.protocolWallet.toString());
console.log('ICO Amount: ', ico_info.totalIcoAmount.toString());
console.log('ICO Token Per SOL: ', ico_info.tokenPerSol.toString());
const ico_state = await program.account.icoState.fetch(ico_state_pda);
console.log('ICO Remaining: ', ico_state.remainingIcoAmount.toString());
console.log('ICO Total SOL: ', ico_state.totalSol.toString());
console.log("\n");
});
and the program rust code
use anchor_lang::prelude::*;
use anchor_spl::token::{ self, Mint, TokenAccount };
use crate::{ state::*, utils::transfer_tokens };
pub fn handler(ctx: Context<Initialize>, ico_amount: u64, token_per_sol: u64) -> Result<()> {
let ico_info_pda = &mut ctx.accounts.ico_info_pda;
let ico_state_pda = &mut ctx.accounts.ico_state_pda;
// let (authority, bump) = Pubkey::find_program_address(&[b"ICO-Authority"], ctx.program_id);
// ico_info_pda.admin = ctx.accounts.admin.key();
ico_info_pda.authority = ctx.accounts.authority.key();
// ico_info_pda.bump = bump;
// ico_info_pda.protocol_wallet = protocol_wallet;
ico_info_pda.protocol_ico_token_pda = ico_info_pda.protocol_ico_token_pda.key();
ico_info_pda.total_ico_amount = ico_amount;
ico_info_pda.token_per_sol = token_per_sol;
ico_state_pda.remaining_ico_amount = ico_amount;
ico_state_pda.total_sol = 0;
let _ = transfer_tokens(
ctx.accounts.admin_ico_token_account.to_account_info(),
ctx.accounts.protocol_ico_token_pda.to_account_info(),
ctx.accounts.authority.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ico_amount
);
Ok(())
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = authority, space = ICOInfo::LEN, seeds = [b"test_ico"], bump)]
pub ico_info_pda: Box<Account<'info, ICOInfo>>,
#[account(init, payer = authority, space = ICOState::LEN, seeds = [b"ico_state"], bump)]
pub ico_state_pda: Box<Account<'info, ICOState>>,
#[account(mut)]
pub authority: Signer<'info>,
#[account(mut)]
pub admin_ico_token_account: Box<Account<'info, TokenAccount>>,
#[account(
init,
payer = authority,
token::mint = ico_token_mint,
token::authority = ico_info_pda,
seeds = [b"protocol_ico_token"],
bump
)]
// pub protocol_ico_token_pda: Box<Account<'info, TokenAccount>>,
/// CHECK:
// #[account(init, payer = authority, seeds = [b"protocol_sol_pool"], bump, space = 8)]
// pub protocol_sol_pool_pda: AccountInfo<'info>,
#[account(constraint = ico_token_mint.key() == admin_ico_token_account.mint)]
pub ico_token_mint: Box<Account<'info, Mint>>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, token::Token>,
}
The goal is to initialize PDA account to store SOL which users can deposit, along with initalizing and topping up ICO spl token PDA account.
It looks like passing protocol_sol_pool_pda account as AccountInfo type is somehow affecting the spl transfer call in the initialize method.
I'd appreciate any advice about what I'm doing wrong here.
Thanks!
Update:
admin_ico_token_account = await getOrCreateAssociatedTokenAccount(provider.connection, admin.payer, ico_token_mint, admin.publicKey);