1

I want to test the anchor "has one" authority check so that only the signer that initialized the account can call a function in the smart contract. This way only the wallet that called the initialize function and created the storage account that holds the integer can then later call the increment function and change the integer.

Here is an excerpt of my contract:

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let account: &mut Account<CounterAccount>
            = &mut ctx.accounts.counter_account;
        account.count_number = 0;
        return Ok(());
    }

    pub fn increment(ctx: Context<Increment>) -> Result<()> {
        let account: &mut Account<CounterAccount>
        = &mut ctx.accounts.counter_account;
        account.count_number += 1;
        return Ok(());
    }

#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(mut, has_one = authority)]
    counter_account: Account<'info, CounterAccount>,
    authority: Signer<'info>
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer=authority, space=64)]
    counter_account: Account<'info, CounterAccount>,
    #[account(mut)]
    authority: Signer<'info>,
    system_program: Program<'info, System>
}

#[account]
pub struct CounterAccount {
    count_number: i64,
    authority: Pubkey,
}

Basically the increment function will not increment unless the the signer is the same as the stored authority pubkey in the CounterAccount struct stored when the CounterAccount in initialized in the initialized function. This is due to the has one check #[account(mut, has_one = authority)]

Here is the test so far, which initializes the account but fails at the increment test because the signer is unknown or error.

const anchor = require("@project-serum/anchor");
const assert = require("assert");
const { SystemProgram } = anchor.web3;

describe("test", () => {
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);
  const program = anchor.workspace.Test;

  const counterAccount = anchor.web3.Keypair.generate();
  const signer = counterAccount;

//this passes and the account is initialized
  it("Is initialized!", async () => {
    const tx = await program.rpc
    .initialize(
      {
      accounts: {
        counterAccount: counterAccount.publicKey,
        authority: provider.wallet.publicKey,
        systemProgram: SystemProgram.programId,
      },
      signers: [signer]
    }
    );
    console.log("Your transaction signature", tx);
    const account = await program.account.counterAccount
    .fetch(counterAccount.publicKey);
    console.log('Count 0: ', account.countNumber)
    assert.ok(account.countNumber == 0);
// Store the account for the next test.
    _counterAccount = counterAccount;
  });

Even if I add the authority account attribute and try different values for authority and signer,

  it('increments account counter', async () => {
    const counterAccount = _counterAccount;
    await program.methods
    .increment()
    .accounts({
      counterAccount: counterAccount.publicKey,
      authority: signer.publicKey,
    })
     .signers([signer])
    .rpc();
    const account = await program.account.counterAccount
    .fetch(counterAccount.publicKey);
    assert.ok(account.countNumber.eq(new anchor.BN(1)));
  })

the error is Error: AnchorError caused by account: counter_account. Error Code: ConstraintHasOne. Error Number: 2001. Error Message: A has one constraint was violated. Program log: Left: Program log: 11111111111111111111111111111111 Program log: Right: Program log: FxBGVvyUEumS9LzwWnmnpAnFr8joxjprDnHewXH1jNWj

How can I cause the signer to be the same as initialized so that the increment test will pass and has_one check will be fixed?

2 Answers 2

2

It appears as though you're never setting counter_account.authority value when initializing.

This causes the has_one constraint in the increment instruction to compare the value of counter_account.authority (which is a zeroed pubkey==11111111111111111111111111111111) to the pubkey of the signer, and consequently fail, as it doesn't match.

You'll need to set the authority field, sort of like this:

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
    ctx.accounts.counter_account.authority = ctx.accounts.signer.key();
    // do other stuff
    return Ok(());
}
1
  • That's right, added that fix using account.authority = *ctx.accounts.authority.key; Although the increment portion of the test is still failing with Error Number: 2001. Error Message: A has one constraint was violated.. Two different keys are shown rather than the same key as the signer for both initialize and increment. I store the counterAccount in the init test.. what values go to the increment test for counterAccount, authority, and signer?
    – djd
    Commented Feb 1, 2023 at 21:33
0

I got the test to run, comments/improvements would be nice though.

const anchor = require("@project-serum/anchor");
const assert = require("assert");
const { SystemProgram } = anchor.web3;

describe("test", () => {
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);

  const program = anchor.workspace.Test;
  const counterAccount = anchor.web3.Keypair.generate();
  const signer = counterAccount;

  it("Is initialized!", async () => {
    
    const tx = await program.rpc
    .initialize(
      {
      accounts: {
        counterAccount: counterAccount.publicKey,
        authority: provider.wallet.publicKey,
        systemProgram: SystemProgram.programId,
      },
      signers: [counterAccount]

//leaving this signers: [] empty causes Signature verification failed error, is the counter account the right thing to put in the signer?

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

const account = await program.account.counterAccount
.fetch(counterAccount.publicKey);
console.log('Count 0: ', account.countNumber);
console.log('Authority: ', account.authority);
assert.ok(account.countNumber == 0);
assert.ok(account.authority);

_counterAccount = counterAccount;
// Store the account for the next test.

  });

  it('increments account counter', async () => {
    const counterAccount = _counterAccount;
    await program.methods
    .increment()
    .accounts({
      counterAccount: counterAccount.publicKey,
      authority: provider.wallet.publicKey,
    })
    .signers([])

//Basically, no matter what I put here in the signers, the saved counterAccount from the first initialize test or the const signer from the generated keypair const counterAccount, there was a has_one constraint violated error. I assume by leaving this blank that this passes the provider wallet as the signer. Is there a way to write this out and be more clear if that is the case?

    .rpc();
    const account = await program.account.counterAccount
    .fetch(counterAccount.publicKey);
    assert.ok(account.countNumber.eq(new anchor.BN(1)));
  })
});
1
  • Since you're initing an account at with a keypair, it will need to sign for the transaction, as well as the payer, in order to create the account. If you leave signers blank, the provider wallet will sign if the IDL detects that it's in the instruction as a signer. You can explicitly pass the keypair in as a signer using (provider.wallet as anchor.Wallet).payer or just provider.wallet.payer.
    – Barebox
    Commented Feb 2, 2023 at 22:41

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.