0

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. error message

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);
1
  • could you share the transfer code as well.
    – Jimii
    Commented Jul 26 at 10:12

1 Answer 1

0

The error you encountered suggests that you likely passed in an account that wasn't expected by anchor in the accounts struct

There are a few areas where I think you can start troubleshooting the issue.

How are you deriving the adminIcoTokenAccount? Is it initialized already? is the mint correct

        let adminIcoTokenAccount = getAssociatedTokenAddressSync(
            mint,
            provider.publicKey
        );

I assume that protocolIcoTokenPda is the program's token account. Are you marking the ATA as a PDA when deriving it


        let protocolIcoTokenPda = getAssociatedTokenAddressSync(
            mint,
            provider.publicKey,
            true // here
        );
3
  • I guess adminIcoTokenAccount is derived corretly, I edited the post with it's initialization. Though I'm a bit confused with what you said about protocolIcoTokenPda, as you can see I initalized with findProgramAdressSync as you can find in the code snippet shared in the post, are you saying it's wrong approach? I initalized it as a PDA because when user deposits SOL, he should be able to receive ICO token from the protocol, and I figured if I initalize this ICO token vault as just a wallet token accout(admin maybe) and it doesn't make sense for the user to use that authority. Commented Jul 26 at 23:10
  • is there something I'm missing here? Commented Jul 26 at 23:10
  • Yes. That's probably it. Anchor expects that token account to have the Token program as it's owner
    – Jimii
    Commented Jul 27 at 7:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.