Search code examples
solanaanchor-solanasolana-program-library

ATA ownership changed to token program


I have been spending hours trying to understand why my ATA address owner suddenly changed to the token program instead of my account address. I am using PDA to transfer the token from the PDA ATA account but unable to do so since the PDA is no longer the ATA owner.

I tried testing Anchor to dissect the problem and to find solutions, here are my tests console logs:

Mint test result:

Mint: A2ojTC6aQZYP6bwUq1FmWN9kwaQTB7NKQmMs89j4FUkx
Sender ATA: 2KcR41e2NxnYY5DWDzvgzHiKpSoaZJ55kvBiqU111DaY
Sender ATA owner: 7QzoE1okkpgsn7Rx5pxyGDkXMSc3nsqhWitDHc6c8rKb
program ATA: H9SEYZsU5ao1WoUNoVTQjVMBbJLNjJmKA5N1cGfjxLqE
Supply: 100
PDA: 10
User: 90
    ✔ Mint token! (7001ms)

Mint test script:

 it("Mint token!", async () => {

    mintPubkey = await createMint(
      program.provider.connection, // conneciton
      user, // fee payer
      user.publicKey, // mint authority
      user.publicKey, // freeze authority (you can use `null` to disable it. when you disable it, you can't turn it on again)
      9 // decimals
    );

    console.log("Mint:", mintPubkey.toBase58())

    let tokenAccountPubkeyUser = await getOrCreateAssociatedTokenAccount(program.provider.connection, user, mintPubkey, user.publicKey)

    console.log("Sender ATA:", tokenAccountPubkeyUser.address.toBase58())

    let tokenAuth = await program.provider.connection.getAccountInfo(tokenAccountPubkeyUser.address);

    console.log("Sender ATA owner:", tokenAccountPubkeyUser.owner.toBase58())

    let tokenAccountPubkeyPda = await getOrCreateAssociatedTokenAccount(program.provider.connection, user, mintPubkey, program.programId)

    console.log("program ATA:", tokenAccountPubkeyPda.address.toBase58())

    let txhash = await mintToChecked(
      program.provider.connection, // connection
      user, // fee payer
      mintPubkey, // mint
      tokenAccountPubkeyUser.address, // receiver (sholud be a token account)
      user, // mint authority
      100e9, // amount. if your decimals is 9, you mint 10^9 for 1 token.
      9 // decimals
    );

    let tokenSupply = await program.provider.connection.getTokenSupply(mintPubkey);

    console.log("Supply:", tokenSupply.value.uiAmount)

    txhash = await transferChecked(
      program.provider.connection, // connection
      user, // payer
      tokenAccountPubkeyUser.address, // from (should be a token account)
      mintPubkey, // mint
      tokenAccountPubkeyPda.address, // to (should be a token account)
      user, // from's owner
      10e9, // amount, if your deciamls is 9, send 10^9 for 1 token
      9 // decimals
    );

    let tokenAmount = await program.provider.connection.getTokenAccountBalance(tokenAccountPubkeyPda.address);


    console.log("PDA:", tokenAmount.value.uiAmount)

    let tokenAmountUser = await program.provider.connection.getTokenAccountBalance(tokenAccountPubkeyUser.address);


    console.log("User:", tokenAmountUser.value.uiAmount)

  })

Remove vault test result:

Mint: A2ojTC6aQZYP6bwUq1FmWN9kwaQTB7NKQmMs89j4FUkx
userProfilePDA CanbMWdj5UT8KWCAUwsmMyZFeyG8kWQER2tZQdxTohEK
Last vault: 3
vaultAccountPDA: 8tvqq4zWMGZuoe4tsjuC85WRQY8n5qxZeoyY2Ro7UwGi
vaultInfoPDA: A5E257kztkqdwxeqrjgFzjG2uPmECNX7LD96Vp6Tve7z
tokenProgram: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
receiver ATA: 2KcR41e2NxnYY5DWDzvgzHiKpSoaZJ55kvBiqU111DaY
receiver (user) ATA owner: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
sender (VaultInfo) ATA: BQhNK47ygEYARanGqJnSjKBco3Crot9ihDMJzT8u7yLU
VaultInfo ATA supply: 10
VaultATA owner: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Program owner: BPFLoaderUpgradeab1e11111111111111111111111
vaultInfoPDA owner: GsCu69BThDsobWHorHkNf8h8zobN6VsexiYkwkH2VtfV

Remove vault test script:

it("User Vault removed!", async () => {
    // Add your test here.
    // const tx = await program.methods.initializeUser().rpc();

    console.log("user:", user.publicKey)
    console.log("program:", program.programId)
    console.log("token program:", TOKEN_PROGRAM_ID)

    console.log("Mint:", mintPubkey.toBase58())


    const [userProfilePDA] = await anchor.web3.PublicKey.findProgramAddress([
      utf8.encode("USER_STATE"),
      user.publicKey.toBuffer(),
    ],
      program.programId
    );
    console.log("userProfilePDA", userProfilePDA.toBase58());

    const userProfile = await program.account.userProfile.fetch(userProfilePDA);
    // console.log("UserProfile:", userProfile)
    console.log("Last vault:", userProfile.lastVault)

    const [vaultAccountPDA] = await anchor.web3.PublicKey.findProgramAddress([
      utf8.encode("VAULT_STATE"),
      user.publicKey.toBuffer(),
      new anchor.BN(0).toBuffer()
    ],
      program.programId
    );
    console.log("vaultAccountPDA:", vaultAccountPDA.toBase58());

    const [vaultInfoPDA] = await anchor.web3.PublicKey.findProgramAddress([
      utf8.encode("INFO_STATE"),
      // user.publicKey.toBuffer(),
    ],
      program.programId
    );
    console.log("vaultInfoPDA:", vaultInfoPDA.toBase58());


    let tokenAccountPubkeyUser = await getOrCreateAssociatedTokenAccount(program.provider.connection, user, mintPubkey, user.publicKey)


    console.log("tokenProgram:", TOKEN_PROGRAM_ID.toBase58())
    console.log("receiver ATA:", tokenAccountPubkeyUser.address.toBase58())

    let tokenAuth = await program.provider.connection.getAccountInfo(tokenAccountPubkeyUser.address);

    console.log("receiver (user) ATA owner:", tokenAuth.owner.toBase58())

    let tokenAccountPubkeyVault = await getOrCreateAssociatedTokenAccount(program.provider.connection, user, mintPubkey, vaultInfoPDA, true)


    console.log("sender (VaultInfo) ATA:", tokenAccountPubkeyVault.address.toBase58())


    let txhash = await transferChecked(
      program.provider.connection, // connection
      user, // payer
      tokenAccountPubkeyUser.address, // from (should be a token account)
      mintPubkey, // mint
      tokenAccountPubkeyVault.address, // to (should be a token account)
      user, // from's owner
      10e9, // amount, if your deciamls is 9, send 10^9 for 1 token
      9 // decimals
    );


    let tokenAmount = await program.provider.connection.getTokenAccountBalance(tokenAccountPubkeyVault.address);

    console.log("VaultInfo ATA supply:", tokenAmount.value.uiAmount)

    tokenAuth = await program.provider.connection.getAccountInfo(tokenAccountPubkeyVault.address);

    console.log("VaultATA owner:", tokenAuth.owner.toBase58())

    tokenAuth = await program.provider.connection.getAccountInfo(program.programId);

    console.log("Program owner:", tokenAuth.owner.toBase58())

    tokenAuth = await program.provider.connection.getAccountInfo(vaultInfoPDA);

    console.log("vaultInfoPDA owner:", tokenAuth.owner.toBase58())



    const tx = await program.rpc.removeVault(0, {
      accounts: {
        authority: user.publicKey,
        userProfile: userProfilePDA,
        vaultAccount: vaultAccountPDA,
        vaultInfo: vaultInfoPDA,
        systemProgram: anchor.web3.SystemProgram.programId,
        tokenProgram: TOKEN_PROGRAM_ID,
        from: tokenAccountPubkeyVault.address,
        to: tokenAccountPubkeyUser.address,
        owner: vaultInfoPDA,
        // sender: vaultInfoPDA
      },
      signers: []
    })

    console.log("Your transaction signature", tx);



  });

I am expecting to use the vaultInfoPDA (which should be the owner of the ATA) to send the token out to user removing their vault.

Thanks!


Solution

  • The term owner is overloaded in the context of SPL tokens, which often causes confusion.

    When you're logging the owner of the account after calling getAccountInfo, that gives you the program that owns the account, which must be the SPL Token program. The SPL Token program has the right to change the data in the account.

    Within that account is also data. In that data, bytes 32-64 define the pubkey that can authorize movements from the account, the SPL token "owner". So there's two owners in one account, one defined by the Solana runtime, and another defined by the SPL Token program.

    You can read more about the ownership model at https://docs.solana.com/developing/programming-model/accounts#ownership-and-assignment-to-programs