lang: add seeds::program constraint for PDAs (#1197)
This commit is contained in:
parent
0dfed11eaa
commit
e04f144e12
|
@ -11,6 +11,10 @@ incremented for features.
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* lang: Add `seeds::program` constraint for specifying which program_id to use when deriving PDAs.([#1197](https://github.com/project-serum/anchor/pull/1197))
|
||||||
|
|
||||||
### Breaking
|
### Breaking
|
||||||
|
|
||||||
* lang: rename `loader_account` module to `account_loader` module ([#1279](https://github.com/project-serum/anchor/pull/1279))
|
* lang: rename `loader_account` module to `account_loader` module ([#1279](https://github.com/project-serum/anchor/pull/1279))
|
||||||
|
|
|
@ -30,10 +30,10 @@
|
||||||
"@ethersproject/logger" "^5.5.0"
|
"@ethersproject/logger" "^5.5.0"
|
||||||
hash.js "1.1.7"
|
hash.js "1.1.7"
|
||||||
|
|
||||||
"@project-serum/anchor@^0.19.0":
|
"@project-serum/anchor@^0.20.0":
|
||||||
version "0.19.0"
|
version "0.20.0"
|
||||||
resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.19.0.tgz#79f1fbe7c3134860ccbfe458a0e09daf79644885"
|
resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.20.0.tgz#547f5c0ff7e66809fa7118b2e3abd8087b5ec519"
|
||||||
integrity sha512-cs0LBmJOrL9eJ8MRNqitnzbpCT5QEzVdJmiIjfNV5YaGn1K9vISR7DtISj3Bdl3KBdLqii4CTw1mpHdi8iXUCg==
|
integrity sha512-p1KOiqGBIbNsopMrSVoPwgxR1iPffsdjMNCOysahTPL9whX2CLX9HQCdopHjYaGl7+SdHRuXml6Wahk/wUmC8g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@project-serum/borsh" "^0.2.2"
|
"@project-serum/borsh" "^0.2.2"
|
||||||
"@solana/web3.js" "^1.17.0"
|
"@solana/web3.js" "^1.17.0"
|
||||||
|
|
|
@ -159,7 +159,9 @@ use syn::parse_macro_input;
|
||||||
/// you can pass it in as instruction data and set the bump value like shown in the example,
|
/// you can pass it in as instruction data and set the bump value like shown in the example,
|
||||||
/// using the <code>instruction_data</code> attribute.
|
/// using the <code>instruction_data</code> attribute.
|
||||||
/// Anchor will then check that the bump returned by <code>find_program_address</code> equals
|
/// Anchor will then check that the bump returned by <code>find_program_address</code> equals
|
||||||
/// the bump in the instruction data.
|
/// the bump in the instruction data.<br>
|
||||||
|
/// <code>seeds::program</code> cannot be used together with init because the creation of an
|
||||||
|
/// account requires its signature which for PDAs only the currently executing program can provide.
|
||||||
/// </li>
|
/// </li>
|
||||||
/// </ul>
|
/// </ul>
|
||||||
/// Example:
|
/// Example:
|
||||||
|
@ -228,21 +230,42 @@ use syn::parse_macro_input;
|
||||||
/// <tr>
|
/// <tr>
|
||||||
/// <td>
|
/// <td>
|
||||||
/// <code>#[account(seeds = <seeds>, bump)]</code><br><br>
|
/// <code>#[account(seeds = <seeds>, bump)]</code><br><br>
|
||||||
/// <code>#[account(seeds = <seeds>, bump = <expr>)]</code>
|
/// <code>#[account(seeds = <seeds>, bump, seeds::program = <expr>)]<br><br>
|
||||||
|
/// <code>#[account(seeds = <seeds>, bump = <expr>)]</code><br><br>
|
||||||
|
/// <code>#[account(seeds = <seeds>, bump = <expr>, seeds::program = <expr>)]</code><br><br>
|
||||||
/// </td>
|
/// </td>
|
||||||
/// <td>
|
/// <td>
|
||||||
/// Checks that given account is a PDA derived from the currently executing program,
|
/// Checks that given account is a PDA derived from the currently executing program,
|
||||||
/// the seeds, and if provided, the bump. If not provided, anchor uses the canonical
|
/// the seeds, and if provided, the bump. If not provided, anchor uses the canonical
|
||||||
/// bump. Will be adjusted in the future to allow PDA to be derived from other programs.<br>
|
/// bump. <br>
|
||||||
|
/// Add <code>seeds::program = <expr></code> to derive the PDA from a different
|
||||||
|
/// program than the currently executing one.<br>
|
||||||
/// This constraint behaves slightly differently when used with <code>init</code>.
|
/// This constraint behaves slightly differently when used with <code>init</code>.
|
||||||
/// See its description.
|
/// See its description.
|
||||||
/// <br><br>
|
/// <br><br>
|
||||||
/// Example:
|
/// Example:
|
||||||
/// <pre><code>
|
/// <pre><code>
|
||||||
/// #[account(seeds = [b"example_seed], bump)]
|
/// #[derive(Accounts)]
|
||||||
/// pub canonical_pda: AccountInfo<'info>,
|
/// #[instruction(first_bump: u8, second_bump: u8)]
|
||||||
/// #[account(seeds = [b"other_seed], bump = 142)]
|
/// pub struct Example {
|
||||||
/// pub arbitrary_pda: AccountInfo<'info>
|
/// #[account(seeds = [b"example_seed], bump)]
|
||||||
|
/// pub canonical_pda: AccountInfo<'info>,
|
||||||
|
/// #[account(
|
||||||
|
/// seeds = [b"example_seed],
|
||||||
|
/// bump,
|
||||||
|
/// seeds::program = other_program.key()
|
||||||
|
/// )]
|
||||||
|
/// pub canonical_pda_two: AccountInfo<'info>,
|
||||||
|
/// #[account(seeds = [b"other_seed], bump = first_bump)]
|
||||||
|
/// pub arbitrary_pda: AccountInfo<'info>
|
||||||
|
/// #[account(
|
||||||
|
/// seeds = [b"other_seed],
|
||||||
|
/// bump = second_bump,
|
||||||
|
/// seeds::program = other_program.key()
|
||||||
|
/// )]
|
||||||
|
/// pub arbitrary_pda_two: AccountInfo<'info>,
|
||||||
|
/// pub other_program: Program<'info, OtherProgram>
|
||||||
|
/// }
|
||||||
/// </code></pre>
|
/// </code></pre>
|
||||||
/// </td>
|
/// </td>
|
||||||
/// </tr>
|
/// </tr>
|
||||||
|
|
|
@ -327,6 +327,15 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma
|
||||||
fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream {
|
fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream {
|
||||||
let name = &f.ident;
|
let name = &f.ident;
|
||||||
let s = &mut c.seeds.clone();
|
let s = &mut c.seeds.clone();
|
||||||
|
|
||||||
|
let deriving_program_id = c
|
||||||
|
.program_seed
|
||||||
|
.clone()
|
||||||
|
// If they specified a seeds::program to use when deriving the PDA, use it.
|
||||||
|
.map(|program_id| quote! { #program_id })
|
||||||
|
// Otherwise fall back to the current program's program_id.
|
||||||
|
.unwrap_or(quote! { program_id });
|
||||||
|
|
||||||
// If the seeds came with a trailing comma, we need to chop it off
|
// If the seeds came with a trailing comma, we need to chop it off
|
||||||
// before we interpolate them below.
|
// before we interpolate them below.
|
||||||
if let Some(pair) = s.pop() {
|
if let Some(pair) = s.pop() {
|
||||||
|
@ -340,7 +349,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2
|
||||||
quote! {
|
quote! {
|
||||||
let (__program_signer, __bump) = anchor_lang::solana_program::pubkey::Pubkey::find_program_address(
|
let (__program_signer, __bump) = anchor_lang::solana_program::pubkey::Pubkey::find_program_address(
|
||||||
&[#s],
|
&[#s],
|
||||||
program_id,
|
&#deriving_program_id,
|
||||||
);
|
);
|
||||||
if #name.key() != __program_signer {
|
if #name.key() != __program_signer {
|
||||||
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
|
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
|
||||||
|
@ -362,7 +371,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2
|
||||||
&[
|
&[
|
||||||
Pubkey::find_program_address(
|
Pubkey::find_program_address(
|
||||||
&[#s],
|
&[#s],
|
||||||
program_id,
|
&#deriving_program_id,
|
||||||
).1
|
).1
|
||||||
][..]
|
][..]
|
||||||
]
|
]
|
||||||
|
@ -378,7 +387,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2
|
||||||
quote! {
|
quote! {
|
||||||
let __program_signer = Pubkey::create_program_address(
|
let __program_signer = Pubkey::create_program_address(
|
||||||
&#seeds[..],
|
&#seeds[..],
|
||||||
program_id,
|
&#deriving_program_id,
|
||||||
).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?;
|
).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?;
|
||||||
if #name.key() != __program_signer {
|
if #name.key() != __program_signer {
|
||||||
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
|
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
|
||||||
|
|
|
@ -610,6 +610,7 @@ pub enum ConstraintToken {
|
||||||
MintFreezeAuthority(Context<ConstraintMintFreezeAuthority>),
|
MintFreezeAuthority(Context<ConstraintMintFreezeAuthority>),
|
||||||
MintDecimals(Context<ConstraintMintDecimals>),
|
MintDecimals(Context<ConstraintMintDecimals>),
|
||||||
Bump(Context<ConstraintTokenBump>),
|
Bump(Context<ConstraintTokenBump>),
|
||||||
|
ProgramSeed(Context<ConstraintProgramSeed>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for ConstraintToken {
|
impl Parse for ConstraintToken {
|
||||||
|
@ -687,7 +688,8 @@ pub struct ConstraintInitGroup {
|
||||||
pub struct ConstraintSeedsGroup {
|
pub struct ConstraintSeedsGroup {
|
||||||
pub is_init: bool,
|
pub is_init: bool,
|
||||||
pub seeds: Punctuated<Expr, Token![,]>,
|
pub seeds: Punctuated<Expr, Token![,]>,
|
||||||
pub bump: Option<Expr>, // None => bump was given without a target.
|
pub bump: Option<Expr>, // None => bump was given without a target.
|
||||||
|
pub program_seed: Option<Expr>, // None => use the current program's program_id
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -771,6 +773,11 @@ pub struct ConstraintTokenBump {
|
||||||
bump: Option<Expr>,
|
bump: Option<Expr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ConstraintProgramSeed {
|
||||||
|
program_seed: Expr,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ConstraintAssociatedToken {
|
pub struct ConstraintAssociatedToken {
|
||||||
pub wallet: Expr,
|
pub wallet: Expr,
|
||||||
|
|
|
@ -182,6 +182,43 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
|
||||||
};
|
};
|
||||||
ConstraintToken::Bump(Context::new(ident.span(), ConstraintTokenBump { bump }))
|
ConstraintToken::Bump(Context::new(ident.span(), ConstraintTokenBump { bump }))
|
||||||
}
|
}
|
||||||
|
"seeds" => {
|
||||||
|
if stream.peek(Token![:]) {
|
||||||
|
stream.parse::<Token![:]>()?;
|
||||||
|
stream.parse::<Token![:]>()?;
|
||||||
|
let kw = stream.call(Ident::parse_any)?.to_string();
|
||||||
|
stream.parse::<Token![=]>()?;
|
||||||
|
|
||||||
|
let span = ident
|
||||||
|
.span()
|
||||||
|
.join(stream.span())
|
||||||
|
.unwrap_or_else(|| ident.span());
|
||||||
|
|
||||||
|
match kw.as_str() {
|
||||||
|
"program" => ConstraintToken::ProgramSeed(Context::new(
|
||||||
|
span,
|
||||||
|
ConstraintProgramSeed {
|
||||||
|
program_seed: stream.parse()?,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
_ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stream.parse::<Token![=]>()?;
|
||||||
|
let span = ident
|
||||||
|
.span()
|
||||||
|
.join(stream.span())
|
||||||
|
.unwrap_or_else(|| ident.span());
|
||||||
|
let seeds;
|
||||||
|
let bracket = bracketed!(seeds in stream);
|
||||||
|
ConstraintToken::Seeds(Context::new(
|
||||||
|
span.join(bracket.span).unwrap_or(span),
|
||||||
|
ConstraintSeeds {
|
||||||
|
seeds: seeds.parse_terminated(Expr::parse)?,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
stream.parse::<Token![=]>()?;
|
stream.parse::<Token![=]>()?;
|
||||||
let span = ident
|
let span = ident
|
||||||
|
@ -234,16 +271,6 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
|
||||||
space: stream.parse()?,
|
space: stream.parse()?,
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
"seeds" => {
|
|
||||||
let seeds;
|
|
||||||
let bracket = bracketed!(seeds in stream);
|
|
||||||
ConstraintToken::Seeds(Context::new(
|
|
||||||
span.join(bracket.span).unwrap_or(span),
|
|
||||||
ConstraintSeeds {
|
|
||||||
seeds: seeds.parse_terminated(Expr::parse)?,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
"constraint" => ConstraintToken::Raw(Context::new(
|
"constraint" => ConstraintToken::Raw(Context::new(
|
||||||
span,
|
span,
|
||||||
ConstraintRaw {
|
ConstraintRaw {
|
||||||
|
@ -308,6 +335,7 @@ pub struct ConstraintGroupBuilder<'ty> {
|
||||||
pub mint_freeze_authority: Option<Context<ConstraintMintFreezeAuthority>>,
|
pub mint_freeze_authority: Option<Context<ConstraintMintFreezeAuthority>>,
|
||||||
pub mint_decimals: Option<Context<ConstraintMintDecimals>>,
|
pub mint_decimals: Option<Context<ConstraintMintDecimals>>,
|
||||||
pub bump: Option<Context<ConstraintTokenBump>>,
|
pub bump: Option<Context<ConstraintTokenBump>>,
|
||||||
|
pub program_seed: Option<Context<ConstraintProgramSeed>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ty> ConstraintGroupBuilder<'ty> {
|
impl<'ty> ConstraintGroupBuilder<'ty> {
|
||||||
|
@ -338,6 +366,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
||||||
mint_freeze_authority: None,
|
mint_freeze_authority: None,
|
||||||
mint_decimals: None,
|
mint_decimals: None,
|
||||||
bump: None,
|
bump: None,
|
||||||
|
program_seed: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,6 +523,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
||||||
mint_freeze_authority,
|
mint_freeze_authority,
|
||||||
mint_decimals,
|
mint_decimals,
|
||||||
bump,
|
bump,
|
||||||
|
program_seed,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
// Converts Option<Context<T>> -> Option<T>.
|
// Converts Option<Context<T>> -> Option<T>.
|
||||||
|
@ -519,6 +549,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
||||||
bump: into_inner!(bump)
|
bump: into_inner!(bump)
|
||||||
.map(|b| b.bump)
|
.map(|b| b.bump)
|
||||||
.expect("bump must be provided with seeds"),
|
.expect("bump must be provided with seeds"),
|
||||||
|
program_seed: into_inner!(program_seed).map(|id| id.program_seed),
|
||||||
});
|
});
|
||||||
let associated_token = match (associated_token_mint, associated_token_authority) {
|
let associated_token = match (associated_token_mint, associated_token_authority) {
|
||||||
(Some(mint), Some(auth)) => Some(ConstraintAssociatedToken {
|
(Some(mint), Some(auth)) => Some(ConstraintAssociatedToken {
|
||||||
|
@ -620,6 +651,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
||||||
ConstraintToken::MintFreezeAuthority(c) => self.add_mint_freeze_authority(c),
|
ConstraintToken::MintFreezeAuthority(c) => self.add_mint_freeze_authority(c),
|
||||||
ConstraintToken::MintDecimals(c) => self.add_mint_decimals(c),
|
ConstraintToken::MintDecimals(c) => self.add_mint_decimals(c),
|
||||||
ConstraintToken::Bump(c) => self.add_bump(c),
|
ConstraintToken::Bump(c) => self.add_bump(c),
|
||||||
|
ConstraintToken::ProgramSeed(c) => self.add_program_seed(c),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -725,6 +757,33 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_program_seed(&mut self, c: Context<ConstraintProgramSeed>) -> ParseResult<()> {
|
||||||
|
if self.program_seed.is_some() {
|
||||||
|
return Err(ParseError::new(c.span(), "seeds::program already provided"));
|
||||||
|
}
|
||||||
|
if self.seeds.is_none() {
|
||||||
|
return Err(ParseError::new(
|
||||||
|
c.span(),
|
||||||
|
"seeds must be provided before seeds::program",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Some(ref init) = self.init {
|
||||||
|
if init.if_needed {
|
||||||
|
return Err(ParseError::new(
|
||||||
|
c.span(),
|
||||||
|
"seeds::program cannot be used with init_if_needed",
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return Err(ParseError::new(
|
||||||
|
c.span(),
|
||||||
|
"seeds::program cannot be used with init",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.program_seed.replace(c);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn add_token_authority(&mut self, c: Context<ConstraintTokenAuthority>) -> ParseResult<()> {
|
fn add_token_authority(&mut self, c: Context<ConstraintTokenAuthority>) -> ParseResult<()> {
|
||||||
if self.token_authority.is_some() {
|
if self.token_authority.is_some() {
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
|
|
|
@ -15,5 +15,8 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "anchor test"
|
"test": "anchor test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"mocha": "^9.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -379,3 +379,25 @@ pub struct InitIfNeededChecksRentExemption<'info> {
|
||||||
pub system_program: Program<'info, System>
|
pub system_program: Program<'info, System>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
#[instruction(bump: u8, second_bump: u8)]
|
||||||
|
pub struct TestProgramIdConstraint<'info> {
|
||||||
|
// not a real associated token account
|
||||||
|
// just deriving like this for testing purposes
|
||||||
|
#[account(seeds = [b"seed"], bump = bump, seeds::program = anchor_spl::associated_token::ID)]
|
||||||
|
first: AccountInfo<'info>,
|
||||||
|
|
||||||
|
#[account(seeds = [b"seed"], bump = second_bump, seeds::program = crate::ID)]
|
||||||
|
second: AccountInfo<'info>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct TestProgramIdConstraintUsingFindPda<'info> {
|
||||||
|
// not a real associated token account
|
||||||
|
// just deriving like this for testing purposes
|
||||||
|
#[account(seeds = [b"seed"], bump, seeds::program = anchor_spl::associated_token::ID)]
|
||||||
|
first: AccountInfo<'info>,
|
||||||
|
|
||||||
|
#[account(seeds = [b"seed"], bump, seeds::program = crate::ID)]
|
||||||
|
second: AccountInfo<'info>,
|
||||||
|
}
|
||||||
|
|
|
@ -268,4 +268,18 @@ pub mod misc {
|
||||||
pub fn init_if_needed_checks_rent_exemption(_ctx: Context<InitIfNeededChecksRentExemption>) -> ProgramResult {
|
pub fn init_if_needed_checks_rent_exemption(_ctx: Context<InitIfNeededChecksRentExemption>) -> ProgramResult {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn test_program_id_constraint(
|
||||||
|
_ctx: Context<TestProgramIdConstraint>,
|
||||||
|
_bump: u8,
|
||||||
|
_second_bump: u8
|
||||||
|
) -> ProgramResult {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_program_id_constraint_find_pda(
|
||||||
|
_ctx: Context<TestProgramIdConstraintUsingFindPda>,
|
||||||
|
) -> ProgramResult {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1528,4 +1528,104 @@ describe("misc", () => {
|
||||||
assert.equal("A rent exempt constraint was violated", err.msg);
|
assert.equal("A rent exempt constraint was violated", err.msg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Can validate PDAs derived from other program ids", () => {
|
||||||
|
it("With bumps using create_program_address", async () => {
|
||||||
|
const [firstPDA, firstBump] =
|
||||||
|
await anchor.web3.PublicKey.findProgramAddress(
|
||||||
|
[anchor.utils.bytes.utf8.encode("seed")],
|
||||||
|
ASSOCIATED_TOKEN_PROGRAM_ID
|
||||||
|
);
|
||||||
|
const [secondPDA, secondBump] =
|
||||||
|
await anchor.web3.PublicKey.findProgramAddress(
|
||||||
|
[anchor.utils.bytes.utf8.encode("seed")],
|
||||||
|
program.programId
|
||||||
|
);
|
||||||
|
|
||||||
|
// correct bump but wrong address
|
||||||
|
const wrongAddress = anchor.web3.Keypair.generate().publicKey;
|
||||||
|
try {
|
||||||
|
await program.rpc.testProgramIdConstraint(firstBump, secondBump, {
|
||||||
|
accounts: {
|
||||||
|
first: wrongAddress,
|
||||||
|
second: secondPDA,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.ok(false);
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.code, 2006);
|
||||||
|
}
|
||||||
|
|
||||||
|
// matching bump seed for wrong address but derived from wrong program
|
||||||
|
try {
|
||||||
|
await program.rpc.testProgramIdConstraint(secondBump, secondBump, {
|
||||||
|
accounts: {
|
||||||
|
first: secondPDA,
|
||||||
|
second: secondPDA,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.ok(false);
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.code, 2006);
|
||||||
|
}
|
||||||
|
|
||||||
|
// correct inputs should lead to successful tx
|
||||||
|
await program.rpc.testProgramIdConstraint(firstBump, secondBump, {
|
||||||
|
accounts: {
|
||||||
|
first: firstPDA,
|
||||||
|
second: secondPDA,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("With bumps using find_program_address", async () => {
|
||||||
|
const firstPDA = (
|
||||||
|
await anchor.web3.PublicKey.findProgramAddress(
|
||||||
|
[anchor.utils.bytes.utf8.encode("seed")],
|
||||||
|
ASSOCIATED_TOKEN_PROGRAM_ID
|
||||||
|
)
|
||||||
|
)[0];
|
||||||
|
const secondPDA = (
|
||||||
|
await anchor.web3.PublicKey.findProgramAddress(
|
||||||
|
[anchor.utils.bytes.utf8.encode("seed")],
|
||||||
|
program.programId
|
||||||
|
)
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
// random wrong address
|
||||||
|
const wrongAddress = anchor.web3.Keypair.generate().publicKey;
|
||||||
|
try {
|
||||||
|
await program.rpc.testProgramIdConstraintFindPda({
|
||||||
|
accounts: {
|
||||||
|
first: wrongAddress,
|
||||||
|
second: secondPDA,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.ok(false);
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.code, 2006);
|
||||||
|
}
|
||||||
|
|
||||||
|
// same seeds but derived from wrong program
|
||||||
|
try {
|
||||||
|
await program.rpc.testProgramIdConstraintFindPda({
|
||||||
|
accounts: {
|
||||||
|
first: secondPDA,
|
||||||
|
second: secondPDA,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.ok(false);
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.code, 2006);
|
||||||
|
}
|
||||||
|
|
||||||
|
// correct inputs should lead to successful tx
|
||||||
|
await program.rpc.testProgramIdConstraintFindPda({
|
||||||
|
accounts: {
|
||||||
|
first: firstPDA,
|
||||||
|
second: secondPDA,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue