Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/reusable-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,8 @@ jobs:
path: tests/test-instruction-validation
- cmd: cd tests/signature-verification && anchor test
path: tests/signature-verification
- cmd: cd tests/pda-payer && anchor test
path: tests/pda-payer
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup/
Expand Down
750 changes: 540 additions & 210 deletions lang/syn/src/codegen/accounts/constraints.rs

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"multiple-suites",
"multiple-suites-run-single",
"bpf-upgradeable-state",
"signature-verification"
"signature-verification",
"pda-payer"
],
"dependencies": {
"@project-serum/common": "^0.0.1-beta.3",
Expand Down
2 changes: 2 additions & 0 deletions tests/pda-payer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.yarn
.pnp*
16 changes: 16 additions & 0 deletions tests/pda-payer/Anchor.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[features]
seeds = true

[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

[programs.localnet]
pda_payer = "55cqWEVF1WMMBxzW6BPHTV2KGZ5NT9454sjfU58j2a4j"

[workspace]
members = ["programs/pda-payer"]

[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

9 changes: 9 additions & 0 deletions tests/pda-payer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[workspace]
members = [
"programs/*"
]
resolver = "2"

[profile.release]
overflow-checks = true

17 changes: 17 additions & 0 deletions tests/pda-payer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "pda-payer",
"version": "0.1.0",
"description": "Created with Anchor",
"license": "(MIT OR Apache-2.0)",
"homepage": "https://github.com/coral-xyz/anchor#readme",
"bugs": {
"url": "https://github.com/coral-xyz/anchor/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/coral-xyz/anchor.git"
},
"scripts": {
"test": "anchor test"
}
}
21 changes: 21 additions & 0 deletions tests/pda-payer/programs/pda-payer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "pda-payer"
version = "0.1.0"
description = "Created with Anchor"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]
name = "pda_payer"

[features]
no-entrypoint = []
no-idl = []
cpi = ["no-entrypoint"]
default = []
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]

[dependencies]
anchor-lang = { path = "../../../../lang" }
anchor-spl = { path = "../../../../spl" }

39 changes: 39 additions & 0 deletions tests/pda-payer/programs/pda-payer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use anchor_lang::prelude::*;

declare_id!("55cqWEVF1WMMBxzW6BPHTV2KGZ5NT9454sjfU58j2a4j");

#[program]
pub mod pda_payer {
use super::*;

pub fn init_with_pda_payer(ctx: Context<MyInstruction>) -> Result<()> {
ctx.accounts.new_account.data = 42;
Ok(())
}
}

#[account]
pub struct MyData {
pub data: u64,
}

#[derive(Accounts)]
pub struct MyInstruction<'info> {
// Seeds must be declared here
#[account(
mut,
seeds = [b"my-pda"],
bump,
)]
pub pda_account: SystemAccount<'info>,

// Then used as payer below
#[account(
init,
payer = pda_account, // PDA as payer
space = 8 + 32,
)]
pub new_account: Account<'info, MyData>,

pub system_program: Program<'info, System>,
}
100 changes: 100 additions & 0 deletions tests/pda-payer/tests/pda-payer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { PdaPayer } from "../target/types/pda_payer";
import { PublicKey, SystemProgram, Keypair } from "@solana/web3.js";
import { expect } from "chai";

describe("pda-payer", () => {
// Configure the client to use the local cluster.
const provider = anchor.AnchorProvider.local();
anchor.setProvider(provider);

const program = anchor.workspace.PdaPayer as Program<PdaPayer>;
const wallet = provider.wallet;

before(async () => {
const airdropSig = await provider.connection.requestAirdrop(
wallet.publicKey,
10 * anchor.web3.LAMPORTS_PER_SOL
);
await provider.connection.confirmTransaction(airdropSig);
});

it("Initializes account with PDA as payer", async () => {
// Find PDA address
const [pdaAccount, bump] = PublicKey.findProgramAddressSync(
[Buffer.from("my-pda")],
program.programId
);

// Fund the PDA account so it can pay for the new account
// We need to transfer funds from the wallet to the PDA since PDAs can't receive airdrops
const fundTx = await provider.sendAndConfirm(
new anchor.web3.Transaction().add(
SystemProgram.transfer({
fromPubkey: provider.wallet.publicKey,
toPubkey: pdaAccount,
lamports: 2 * anchor.web3.LAMPORTS_PER_SOL,
})
)
);

// Derive the new account address (it will be created by the init constraint)
// For init, we need to provide a keypair for the new account
const newAccount = Keypair.generate();

// Get the PDA account info to check it has funds
// Note: The PDA account might not exist yet, so we check after funding
let pdaAccountInfo = await provider.connection.getAccountInfo(pdaAccount);

// If PDA doesn't exist yet, it will be created when we transfer funds to it
// After the transfer, it should exist and have funds
if (pdaAccountInfo === null) {
// Wait a bit for the transaction to be confirmed
await new Promise((resolve) => setTimeout(resolve, 1000));
pdaAccountInfo = await provider.connection.getAccountInfo(pdaAccount);
}

expect(pdaAccountInfo).to.not.be.null;
expect(pdaAccountInfo!.lamports).to.be.greaterThan(0);

try {
// Call the instruction
// The newAccount keypair is needed for init, but the PDA will pay for the account creation
const tx = await program.methods
.initWithPdaPayer()
.accounts({
pdaAccount: pdaAccount,
newAccount: newAccount.publicKey,
systemProgram: SystemProgram.programId,
})
.signers([newAccount])
.rpc();

console.log("Transaction signature:", tx);

// Verify the new account was created
const newAccountInfo = await provider.connection.getAccountInfo(
newAccount.publicKey
);
expect(newAccountInfo).to.not.be.null;

// Verify the account data
const accountData = await program.account.myData.fetch(
newAccount.publicKey
);
expect(accountData.data.toNumber()).to.equal(42);

// Verify the PDA account was used as payer (its balance should have decreased)
const pdaAccountInfoAfter = await provider.connection.getAccountInfo(
pdaAccount
);
expect(pdaAccountInfoAfter!.lamports).to.be.lessThan(
pdaAccountInfo!.lamports
);
} catch (err) {
console.error("Error:", err);
throw err;
}
});
});
11 changes: 11 additions & 0 deletions tests/pda-payer/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"types": ["mocha", "chai", "node"],
"typeRoots": ["./node_modules/@types"],
"lib": ["es2015"],
"module": "commonjs",
"target": "es6",
"esModuleInterop": true,
"skipLibCheck": true
}
}