Skip to content

Commit

Permalink
Add spl token gating feature to fixed-price-sale program (metaplex-fo…
Browse files Browse the repository at this point in the history
…undation#448)

* feat: add spl token gating feature to fixed-price-sale program, tests included

* fix: native treasury holder balance check

* fix: update program idl

* fixes js sdk

* lint

* fixed pyaout ticket

* tests unstable on GH, working locally

Co-authored-by: austbot <[email protected]>
Co-authored-by: Austin Adams <[email protected]>
  • Loading branch information
3 people authored May 18, 2022
1 parent 4805ed8 commit ab062d7
Show file tree
Hide file tree
Showing 21 changed files with 835 additions and 93 deletions.
34 changes: 25 additions & 9 deletions .ammanrc.js → .base-ammanrc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

// @ts-check
'use strict';
const path = require('path');
Expand All @@ -10,22 +9,38 @@ function localDeployPath(programName) {
return path.join(localDeployDir, `${programName}.so`);
}

const programs = [
{label: "Metadata", programId: 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s', deployPath: localDeployPath('mpl_token_metadata')},
{label: "Vault", programId:'vau1zxA2LbssAUEF7Gpw91zMM1LvXrvpzJtmZ58rPsn', deployPath: localDeployPath('mpl_token_vault')},
{label: "Auction", programId: 'auctxRXPeJoc4817jDhf4HbjnhEcr1cCXenosMhK5R8', deployPath: localDeployPath('mpl_auction')},
{label: "Metaplex", programId: 'p1exdMJcjVao65QdewkaZRUnU6VPSXhus9n2GzWfh98', deployPath: localDeployPath('mpl_metaplex')},
{
const programs = {
metadata: {
label: "Metadata",
programId: 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s',
deployPath: localDeployPath('mpl_token_metadata')
},
vault: {
label: "Vault",
programId: 'vau1zxA2LbssAUEF7Gpw91zMM1LvXrvpzJtmZ58rPsn',
deployPath: localDeployPath('mpl_token_vault')
},
auction: {
label: "Auction",
programId: 'auctxRXPeJoc4817jDhf4HbjnhEcr1cCXenosMhK5R8',
deployPath: localDeployPath('mpl_auction')
},
metaplex: {
label: "Metaplex",
programId: 'p1exdMJcjVao65QdewkaZRUnU6VPSXhus9n2GzWfh98',
deployPath: localDeployPath('mpl_metaplex')
},
token_sale: {
label: "Fixed Price Token Sale",
programId: 'SaLeTjyUa5wXHnGuewUSyJ5JWZaHwz3TxqUntCE9czo',
deployPath: localDeployPath('mpl_fixed_price_sale'),
},
{
candy_machine: {
label: "Candy Machine",
programId: 'cndy3Z4yapfJBmL3ShUp5exZKqR3z33thTzeNMm2gRZ',
deployPath: localDeployPath('mpl_candy_machine'),
},
];
};

const validator = {
killRunningValidators: true,
Expand All @@ -39,6 +54,7 @@ const validator = {
};

module.exports = {
programs,
validator,
relay: {
enabled: true,
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/program-auction-house.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

env:
CARGO_TERM_COLOR: always
SOLANA_VERSION_STABLE: 1.9.15
SOLANA_VERSION_STABLE: 1.9.17
SOLANA_VERSION_UNSTABLE: 1.10.5
RUST_TOOLCHAIN: stable

Expand Down
17 changes: 5 additions & 12 deletions candy-machine/js/.ammanrc.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
'use strict';
// @ts-check
const base = require('../../.ammanrc.js');
console.log(base.validator.programs.find(({label})=>label === "Candy Machine"))
const validator = {...base.validator, programs: [
base.validator.programs.find((e)=>e.label === "Candy Machine"),
base.validator.programs.find((e)=>e.label === "Metadata"),
]};
module.exports = {
validator,
relay: {
enabled: true,
killRunningRelay: true,
},
const base = require('../../.base-ammanrc.js');
const validator = {
...base.validator,
programs: [base.programs.metadata, base.programs.candy_machine],
};
module.exports = {validator};
9 changes: 4 additions & 5 deletions fixed-price-sale/js/.ammanrc.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use strict';
// @ts-check
const base = require('../../.ammanrc.js');

const base = require('../../.base-ammanrc.js');
const validator = {
...base.validator,
programs: [base.programs.metadata, base.programs.fixedPriceSaleToken],
...base.validator,
programs: [base.programs.metadata, base.programs.token_sale],
};
module.exports = { validator };
module.exports = {validator};
3 changes: 3 additions & 0 deletions fixed-price-sale/js/idl/fixed_price_sale.json
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,9 @@
},
{
"name": "WrongCollectionMintKey"
},
{
"name": "WrongGatingToken"
}
]
}
Expand Down
7 changes: 5 additions & 2 deletions fixed-price-sale/js/src/generated/instructions/buy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export function createBuyInstruction(accounts: BuyInstructionAccounts, args: Buy
masterEditionMetadata,
clock,
tokenMetadataProgram,
additionalKeys,
} = accounts;

const [data] = buyStruct.serialize({
Expand Down Expand Up @@ -202,8 +203,10 @@ export function createBuyInstruction(accounts: BuyInstructionAccounts, args: Buy
},
];

if (accounts.additionalKeys) {
keys.push(...accounts.additionalKeys);
if (additionalKeys && additionalKeys.length > 0) {
additionalKeys.forEach((account) => {
keys.push(account);
});
}

const ix = new web3.TransactionInstruction({
Expand Down
14 changes: 8 additions & 6 deletions fixed-price-sale/js/src/generated/instructions/withdraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export type WithdrawInstructionAccounts = {
payoutTicket: web3.PublicKey;
clock: web3.PublicKey;
associatedTokenProgram: web3.PublicKey;
primaryMetadataCreators?: web3.PublicKey;
primaryMetadataCreators?: web3.PublicKey[];
};

const withdrawInstructionDiscriminator = [183, 18, 70, 156, 148, 109, 161, 34];
Expand Down Expand Up @@ -171,11 +171,13 @@ export function createWithdrawInstruction(
},
];

if (primaryMetadataCreators) {
keys.push({
pubkey: primaryMetadataCreators,
isWritable: false,
isSigner: false,
if (primaryMetadataCreators && primaryMetadataCreators.length > 0) {
primaryMetadataCreators.forEach((pubkey) => {
keys.push({
pubkey,
isWritable: false,
isSigner: false,
});
});
}

Expand Down
1 change: 1 addition & 0 deletions fixed-price-sale/js/src/generated/types/ErrorCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export enum ErrorCode {
WrongGatingDate,
CollectionMintMissing,
WrongCollectionMintKey,
WrongGatingToken,
}

/**
Expand Down
4 changes: 2 additions & 2 deletions fixed-price-sale/js/test/actions/initSellingResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const initSellingResource = async ({
vaultOwnerBump: number;
resourceMint: Keypair;
metadata: PublicKey;
primaryMetadataCreators: PublicKey;
primaryMetadataCreators: PublicKey[];
}> => {
const secondaryCreator: Creator = {
address: payer.publicKey,
Expand Down Expand Up @@ -135,6 +135,6 @@ export const initSellingResource = async ({
vaultOwnerBump,
resourceMint,
metadata,
primaryMetadataCreators,
primaryMetadataCreators: [primaryMetadataCreators],
};
};
1 change: 0 additions & 1 deletion fixed-price-sale/js/test/actions/verifyCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
} from '@metaplex-foundation/mpl-token-metadata';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore createMintToInstruction export actually exist but isn't setup correctly
import { createMintToInstruction } from '@solana/spl-token';
import { strict as assert } from 'assert';
import { createAndSignTransaction } from '../utils';

Expand Down
2 changes: 1 addition & 1 deletion fixed-price-sale/js/test/collection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { verifyCollection } from './actions/verifyCollection';

killStuckProcess();

test('buy: successful purchase for newly minted treasury mint', async (t) => {
test('buy: successful purchase for newly minted treasury mint', async () => {
const { payer, connection, transactionHandler } = await createPrerequisites();

const {
Expand Down
4 changes: 2 additions & 2 deletions fixed-price-sale/js/test/transactions/withdraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface WithdrawParams {
payoutTicketBump: number;
treasuryOwnerBump: number;
treasuryOwner: PublicKey;
primaryMetadataCreators: PublicKey;
primaryMetadataCreators: PublicKey[];
}

export const createWithdrawTransaction = async ({
Expand Down Expand Up @@ -47,7 +47,7 @@ export const createWithdrawTransaction = async ({
payer: payer.publicKey,
payoutTicket: payoutTicket,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
primaryMetadataCreators,
primaryMetadataCreators: primaryMetadataCreators,
clock: SYSVAR_CLOCK_PUBKEY,
},
{
Expand Down
3 changes: 3 additions & 0 deletions fixed-price-sale/program/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,7 @@ pub enum ErrorCode {
// 6042
#[msg("Wrong collection mint key")]
WrongCollectionMintKey,
// 6043
#[msg("Wrong gating token")]
WrongGatingToken,
}
33 changes: 32 additions & 1 deletion fixed-price-sale/program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ pub mod utils;

use crate::{
error::ErrorCode,
state::{GatingConfig, Market, PrimaryMetadataCreators, SellingResource, Store, TradeHistory, PayoutTicket},
state::{
GatingConfig, Market, PayoutTicket, PrimaryMetadataCreators, SellingResource, Store,
TradeHistory,
},
utils::*,
};
use anchor_lang::{prelude::*, AnchorDeserialize, AnchorSerialize, System};
Expand Down Expand Up @@ -157,17 +160,22 @@ pub struct InitSellingResource<'info> {
admin: Signer<'info>,
#[account(init, payer=admin, space=SellingResource::LEN)]
selling_resource: Box<Account<'info, SellingResource>>,
/// CHECK: checked in program
selling_resource_owner: UncheckedAccount<'info>,
resource_mint: Box<Account<'info, Mint>>,
#[account(owner=mpl_token_metadata::id())]
/// CHECK: checked in program
master_edition: UncheckedAccount<'info>,
#[account(owner=mpl_token_metadata::id())]
/// CHECK: checked in program
metadata: UncheckedAccount<'info>,
#[account(mut, has_one=owner)]
vault: Box<Account<'info, TokenAccount>>,
#[account(seeds=[VAULT_OWNER_PREFIX.as_bytes(), resource_mint.key().as_ref(), store.key().as_ref()], bump=vault_owner_bump)]
/// CHECK: checked in program
owner: UncheckedAccount<'info>,
#[account(mut)]
/// CHECK: checked in program
resource_token: UncheckedAccount<'info>,
rent: Sysvar<'info, Rent>,
token_program: Program<'info, Token>,
Expand All @@ -184,10 +192,13 @@ pub struct CreateMarket<'info> {
selling_resource_owner: Signer<'info>,
#[account(mut, has_one=store)]
selling_resource: Box<Account<'info, SellingResource>>,
/// CHECK: checked in program
mint: UncheckedAccount<'info>,
#[account(mut)]
/// CHECK: checked in program
treasury_holder: UncheckedAccount<'info>,
#[account(seeds=[HOLDER_PREFIX.as_bytes(), mint.key().as_ref(), selling_resource.key().as_ref()], bump=treasury_owner_bump)]
/// CHECK: checked in program
owner: UncheckedAccount<'info>,
system_program: Program<'info, System>,
// if gating config is set collection mint key should be passed
Expand All @@ -202,36 +213,45 @@ pub struct Buy<'info> {
#[account(mut)]
selling_resource: Box<Account<'info, SellingResource>>,
#[account(mut)]
/// CHECK: checked in program
user_token_account: UncheckedAccount<'info>,
#[account(mut)]
user_wallet: Signer<'info>,
#[account(init_if_needed, seeds=[HISTORY_PREFIX.as_bytes(), user_wallet.key().as_ref(), market.key().as_ref()], bump, payer=user_wallet)]
trade_history: Box<Account<'info, TradeHistory>>,
#[account(mut)]
/// CHECK: checked in program
treasury_holder: UncheckedAccount<'info>,
// Will be created by `mpl_token_metadata`
#[account(mut)]
/// CHECK: checked in program
new_metadata: UncheckedAccount<'info>,
// Will be created by `mpl_token_metadata`
#[account(mut)]
/// CHECK: checked in program
new_edition: UncheckedAccount<'info>,
#[account(mut, owner=mpl_token_metadata::id())]
/// CHECK: checked in program
master_edition: UncheckedAccount<'info>,
#[account(mut)]
new_mint: Box<Account<'info, Mint>>,
// Will be created by `mpl_token_metadata`
#[account(mut)]
/// CHECK: checked in program
edition_marker: UncheckedAccount<'info>,
#[account(mut, has_one=owner)]
vault: Box<Account<'info, TokenAccount>>,
#[account(seeds=[VAULT_OWNER_PREFIX.as_bytes(), selling_resource.resource.as_ref(), selling_resource.store.as_ref()], bump=vault_owner_bump)]
/// CHECK: checked in program
owner: UncheckedAccount<'info>,
#[account(mut, constraint = new_token_account.owner == user_wallet.key())]
new_token_account: Box<Account<'info, TokenAccount>>,
#[account(mut, owner=mpl_token_metadata::id())]
/// CHECK: checked in program
master_edition_metadata: UncheckedAccount<'info>,
clock: Sysvar<'info, Clock>,
rent: Sysvar<'info, Rent>,
/// CHECK: checked in program
token_metadata_program: UncheckedAccount<'info>,
token_program: Program<'info, Token>,
system_program: Program<'info, System>,
Expand All @@ -249,14 +269,20 @@ pub struct Withdraw<'info> {
market: Box<Account<'info, Market>>,
selling_resource: Box<Account<'info, SellingResource>>,
#[account(owner=mpl_token_metadata::id())]
/// CHECK: checked in program
metadata: UncheckedAccount<'info>,
#[account(mut)]
/// CHECK: checked in program
treasury_holder: UncheckedAccount<'info>,
/// CHECK: checked in program
treasury_mint: UncheckedAccount<'info>,
#[account(seeds=[HOLDER_PREFIX.as_bytes(), market.treasury_mint.as_ref(), market.selling_resource.as_ref()], bump=treasury_owner_bump)]
/// CHECK: checked in program
owner: UncheckedAccount<'info>,
#[account(mut)]
/// CHECK: checked in program
destination: UncheckedAccount<'info>,
/// CHECK: checked in program
funder: UncheckedAccount<'info>,
#[account(mut)]
payer: Signer<'info>,
Expand All @@ -274,20 +300,24 @@ pub struct Withdraw<'info> {
pub struct ClaimResource<'info> {
#[account(has_one=selling_resource, has_one=treasury_holder)]
market: Account<'info, Market>,
/// CHECK: checked in program
treasury_holder: UncheckedAccount<'info>,
#[account(has_one=vault, constraint = selling_resource.owner == selling_resource_owner.key())]
selling_resource: Account<'info, SellingResource>,
selling_resource_owner: Signer<'info>,
#[account(mut, has_one=owner)]
vault: Box<Account<'info, TokenAccount>>,
#[account(mut, owner=mpl_token_metadata::id())]
/// CHECK: checked in program
metadata: UncheckedAccount<'info>,
#[account(seeds=[VAULT_OWNER_PREFIX.as_bytes(), selling_resource.resource.as_ref(), selling_resource.store.as_ref()], bump=vault_owner_bump)]
/// CHECK: checked in program
owner: UncheckedAccount<'info>,
#[account(mut)]
destination: Box<Account<'info, TokenAccount>>,
clock: Sysvar<'info, Clock>,
token_program: Program<'info, Token>,
/// CHECK: checked in program
token_metadata_program: UncheckedAccount<'info>,
system_program: Program<'info, System>,
}
Expand Down Expand Up @@ -334,6 +364,7 @@ pub struct SavePrimaryMetadataCreators<'info> {
#[account(mut)]
admin: Signer<'info>,
#[account(mut, owner=mpl_token_metadata::id())]
/// CHECK: checked in program
metadata: UncheckedAccount<'info>,
#[account(init, space=PrimaryMetadataCreators::LEN, payer=admin, seeds=[PRIMARY_METADATA_CREATORS_PREFIX.as_bytes(), metadata.key.as_ref()], bump)]
primary_metadata_creators: Box<Account<'info, PrimaryMetadataCreators>>,
Expand Down
Loading

0 comments on commit ab062d7

Please sign in to comment.