I was following along in this tutorial and found this user's approach. Is there a better way to do this?
2 Answers
The usual way of using testing using a different user is, first you would create the keypair of the user.
const newUser = anchor.web3.Keypair.generate();
Then you would have to fund this user account so that they can pay the gas fees and sign the transaction. This would go inside a test, something like
it("initialize program state", async () => {
await provider.connection.confirmTransaction(
await provider.connection.requestAirdrop(
newUser.publicKey,
10000000000
),
"confirmed"
);
);
Then, all you would need to do is add newUser
in the signers
array while making the rpc call.
Sometimes testing with multiple users becomes necessary when, for example you have a program which allows only certain whitelisted addresses to sign certain transactions. Then you would need to test those cases using multiple users.
-
hmmm that doesn't seem to work: airdropping 100000000000 to 3nSQyCdA7aFDzjWTjpWXjxcGJNu94boKqMVWiU6TzJM1 airdropped 100000000000 Error: unknown signer: 3nSQyCdA7aFDzjWTjpWXjxcGJNu94boKqMVWiU6TzJM1 the call looks like this: const tx = await dvw.methods .initialize() .accounts({ user: user0.publicKey, systemProgram: anchor.web3.SystemProgram.programId, }) .signers([user0]) .rpc() .catch(console.error); Commented Jul 14, 2022 at 14:15
-
omg how the F do you do multiline code comments here?! losing my mind! Commented Jul 14, 2022 at 14:19
-
are u airdropping on devnet cause u only airdrop 2 sol or 2000000000 lamports on devnet. Commented Jul 14, 2022 at 15:56
-
1@DhruvDJain If
solana config
is set tolocalhost
, then you can airdrop as many lamports as you want to. Commented Jul 14, 2022 at 16:28
So it looks like @anon.sol was correct. I think the original difficulty was in dealing with passing in PDA accounts to transactions before they were created. Regardless, using anchor.web3.Keypair.generate()
and funding it will work. Here is working example code for anyone else running into this issue in the future. The program is the same from the linked code in the original question:
import * as anchor from "@project-serum/anchor";
import { Program } from "@project-serum/anchor";
import { DoorsVsWheels } from "../target/types/doors_vs_wheels";
import { LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
import { expect } from "chai";
const VOTING_FEE = 2016120;
const airdropAmount = 100 * LAMPORTS_PER_SOL;
const addFunds = async (
provider: anchor.Provider,
user: anchor.web3.PublicKey,
amount: number
): Promise<void> => {
const airdropTxHash = await provider.connection.requestAirdrop(user, amount);
await provider.connection.confirmTransaction(airdropTxHash, "confirmed");
console.log(`airdropped ${amount} to ${user.toBase58()}`);
};
describe("when using doors-vs-wheels...", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const dvw = anchor.workspace.DoorsVsWheels as Program<DoorsVsWheels>;
const user0 = anchor.web3.Keypair.generate();
const user1 = anchor.web3.Keypair.generate();
let votesCounterBalance: number;
let votesCounter: anchor.web3.PublicKey;
before(async () => {
// votesCounter is a PDA created in the program we still need it here because of solana txs needing to know about all accounts touched ahead of time
const [vc, _] = await PublicKey.findProgramAddress(
[anchor.utils.bytes.utf8.encode("votes_counter")],
dvw.programId
);
votesCounter = vc;
});
it("should intialize as user0", async () => {
await addFunds(provider, user0.publicKey, airdropAmount);
await dvw.methods
.initialize()
.accounts({
user: user0.publicKey,
votesCounter, // this doesn't exist yet but we still need it here AND it needs to match what the program will compute!
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([user0])
.rpc();
const votesCounterStorage = await dvw.account.votesCounter.fetch(
votesCounter
);
votesCounterBalance = await provider.connection.getBalance(votesCounter);
expect(votesCounterStorage.wheels.toNumber()).to.equal(0);
expect(votesCounterStorage.doors.toNumber()).to.equal(0);
expect(votesCounterBalance).to.not.equal(0);
});
it("should vote for wheels and pay service fee as user0", async () => {
const [userVote, _] = await PublicKey.findProgramAddress(
[anchor.utils.bytes.utf8.encode("user_vote"), user0.publicKey.toBuffer()],
dvw.programId
);
await dvw.methods
.vote({ wheels: {} })
.accounts({
user: user0.publicKey,
userVote,
votesCounter,
})
.signers([user0])
.rpc();
});
it("should vote for doors and pay service fee as user1", async () => {
await addFunds(provider, user1.publicKey, airdropAmount);
const [userVote, _] = await PublicKey.findProgramAddress(
[anchor.utils.bytes.utf8.encode("user_vote"), user1.publicKey.toBuffer()],
dvw.programId
);
await dvw.methods
.vote({ wheels: {} })
.accounts({
user: user1.publicKey,
userVote,
votesCounter,
})
.signers([user1])
.rpc();
});
});
anchor.web3.Keypair.generate();
and just add that as a signer in the call. You will still have the signer attached to the provider, but you will also have a needed signature of the non-provider keypair to test against. so you can essentially treat it as someone is signing off on a tx on the other user's behalf and paying their tx fees. Is this a correct mental model to continue down this road with?