reservation list fix for scaling (#70)
This commit is contained in:
parent
eab0796c35
commit
5a4086738b
|
@ -148,17 +148,20 @@ export class ReservationList {
|
|||
/// What supply counter was on master_edition when this reservation was created.
|
||||
supplySnapshot: BN | null;
|
||||
reservations: Reservation[];
|
||||
totalReservationSpots: BN;
|
||||
|
||||
constructor(args: {
|
||||
key: MetadataKey;
|
||||
masterEdition: PublicKey;
|
||||
supplySnapshot: BN | null;
|
||||
reservations: Reservation[];
|
||||
totalReservationSpots: BN;
|
||||
}) {
|
||||
this.key = MetadataKey.EditionV1;
|
||||
this.masterEdition = args.masterEdition;
|
||||
this.supplySnapshot = args.supplySnapshot;
|
||||
this.reservations = args.reservations;
|
||||
this.totalReservationSpots = args.totalReservationSpots;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -402,6 +405,7 @@ export const METADATA_SCHEMA = new Map<any, any>([
|
|||
['masterEdition', 'pubkey'],
|
||||
['supplySnapshot', { kind: 'option', type: 'u64' }],
|
||||
['reservations', [Reservation]],
|
||||
['totalReservationSpots', 'u64'],
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -21,6 +21,43 @@ use {
|
|||
},
|
||||
};
|
||||
|
||||
fn set_reservation_list_wrapper<'a>(
|
||||
program_id: &'a Pubkey,
|
||||
master_edition_info: &AccountInfo<'a>,
|
||||
reservation_list_info: &AccountInfo<'a>,
|
||||
auction_manager_info: &AccountInfo<'a>,
|
||||
signer_seeds: &[&[u8]],
|
||||
reservations: Vec<Reservation>,
|
||||
first_push: bool,
|
||||
total_reservation_spots: u64,
|
||||
) -> ProgramResult {
|
||||
let total_reservation_spot_opt: Option<u64>;
|
||||
|
||||
if first_push {
|
||||
total_reservation_spot_opt = Some(total_reservation_spots)
|
||||
} else {
|
||||
total_reservation_spot_opt = None
|
||||
}
|
||||
invoke_signed(
|
||||
&set_reservation_list(
|
||||
*program_id,
|
||||
*master_edition_info.key,
|
||||
*reservation_list_info.key,
|
||||
*auction_manager_info.key,
|
||||
reservations,
|
||||
total_reservation_spot_opt,
|
||||
),
|
||||
&[
|
||||
master_edition_info.clone(),
|
||||
reservation_list_info.clone(),
|
||||
auction_manager_info.clone(),
|
||||
],
|
||||
&[&signer_seeds],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn reserve_list_if_needed<'a>(
|
||||
program_id: &'a Pubkey,
|
||||
|
@ -42,7 +79,7 @@ pub fn reserve_list_if_needed<'a>(
|
|||
// but may be invocation someday. It's inefficient style but better for the interface maintenance
|
||||
// in the long run if we move to better storage solutions (so that this action doesnt need to change if
|
||||
// storage does.)
|
||||
|
||||
let mut total_reservation_spots: u64 = 0;
|
||||
for n in 0..auction_manager.settings.winning_configs.len() {
|
||||
match auction.winner_at(n) {
|
||||
Some(address) => {
|
||||
|
@ -55,6 +92,9 @@ pub fn reserve_list_if_needed<'a>(
|
|||
})
|
||||
.map(|i| i.amount as u64)
|
||||
.sum();
|
||||
total_reservation_spots = total_reservation_spots
|
||||
.checked_add(spots)
|
||||
.ok_or(MetaplexError::NumericalOverflowError)?;
|
||||
reservations.push(Reservation {
|
||||
address,
|
||||
// Select all items in a winning config matching the same safety deposit box
|
||||
|
@ -62,27 +102,45 @@ pub fn reserve_list_if_needed<'a>(
|
|||
// and then sum them to get the total spots to reserve for this winner
|
||||
spots_remaining: spots,
|
||||
total_spots: spots,
|
||||
})
|
||||
});
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
invoke_signed(
|
||||
&set_reservation_list(
|
||||
*program_id,
|
||||
*master_edition_info.key,
|
||||
*reservation_list_info.key,
|
||||
*auction_manager_info.key,
|
||||
reservations,
|
||||
),
|
||||
&[
|
||||
master_edition_info.clone(),
|
||||
reservation_list_info.clone(),
|
||||
auction_manager_info.clone(),
|
||||
],
|
||||
&[&signer_seeds],
|
||||
)?;
|
||||
let mut first_push = true;
|
||||
let mut reservation_queue: Vec<Reservation> = vec![];
|
||||
for reservation in reservations {
|
||||
reservation_queue.push(reservation);
|
||||
if reservation_queue.len().checked_rem(30) == Some(0) && reservation_queue.len() > 0 {
|
||||
set_reservation_list_wrapper(
|
||||
program_id,
|
||||
master_edition_info,
|
||||
reservation_list_info,
|
||||
auction_manager_info,
|
||||
signer_seeds,
|
||||
reservation_queue,
|
||||
first_push,
|
||||
total_reservation_spots,
|
||||
)?;
|
||||
|
||||
first_push = false;
|
||||
reservation_queue = vec![]; // start over with new list.
|
||||
}
|
||||
}
|
||||
|
||||
if reservation_queue.len() > 0 {
|
||||
set_reservation_list_wrapper(
|
||||
program_id,
|
||||
master_edition_info,
|
||||
reservation_list_info,
|
||||
auction_manager_info,
|
||||
signer_seeds,
|
||||
reservation_queue,
|
||||
first_push,
|
||||
total_reservation_spots,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -266,6 +266,14 @@ pub enum MetadataError {
|
|||
/// Data type mismatch
|
||||
#[error("Data type mismatch")]
|
||||
DataTypeMismatch,
|
||||
|
||||
/// Beyond alotted address size in reservation!
|
||||
#[error("Beyond alotted address size in reservation!")]
|
||||
BeyondAlottedAddressSize,
|
||||
|
||||
/// The reservation has only been partially alotted
|
||||
#[error("The reservation has only been partially alotted")]
|
||||
ReservationNotComplete,
|
||||
}
|
||||
|
||||
impl PrintProgramError for MetadataError {
|
||||
|
|
|
@ -45,6 +45,8 @@ pub struct MintPrintingTokensViaTokenArgs {
|
|||
pub struct SetReservationListArgs {
|
||||
/// If set, means that no more than this number of editions can ever be minted. This is immutable.
|
||||
pub reservations: Vec<Reservation>,
|
||||
/// should only be present on the very first call to set reservation list.
|
||||
pub total_reservation_spots: Option<u64>,
|
||||
}
|
||||
|
||||
/// Instructions supported by the Metadata program.
|
||||
|
@ -124,6 +126,10 @@ pub enum MetadataInstruction {
|
|||
/// with the pda that was created by that first bidder - the token metadata can then cross reference
|
||||
/// these people with the list and see that bidder A gets edition #2, so on and so forth.
|
||||
///
|
||||
/// NOTE: If you have more than 30 addresses in a reservation list, this may be called multiple times to build up the list,
|
||||
/// otherwise, it simply wont fit in one transaction. Only provide a total_reservation argument on the first call, which will
|
||||
/// allocate the edition space, and in follow up calls this will specifically be unnecessary (and indeed will error.)
|
||||
///
|
||||
/// 0. `[writable]` Master Edition key (pda of ['metadata', program id, mint id, 'edition'])
|
||||
/// 1. `[writable]` PDA for ReservationList of ['metadata', program id, master edition key, 'reservation', resource-key]
|
||||
/// 3. `[signer]` The resource you tied the reservation list too
|
||||
|
@ -363,6 +369,7 @@ pub fn set_reservation_list(
|
|||
reservation_list: Pubkey,
|
||||
resource: Pubkey,
|
||||
reservations: Vec<Reservation>,
|
||||
total_reservation_spots: Option<u64>,
|
||||
) -> Instruction {
|
||||
Instruction {
|
||||
program_id,
|
||||
|
@ -371,9 +378,12 @@ pub fn set_reservation_list(
|
|||
AccountMeta::new(reservation_list, false),
|
||||
AccountMeta::new_readonly(resource, true),
|
||||
],
|
||||
data: MetadataInstruction::SetReservationList(SetReservationListArgs { reservations })
|
||||
.try_to_vec()
|
||||
.unwrap(),
|
||||
data: MetadataInstruction::SetReservationList(SetReservationListArgs {
|
||||
reservations,
|
||||
total_reservation_spots,
|
||||
})
|
||||
.try_to_vec()
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,12 @@ pub fn process_instruction(
|
|||
}
|
||||
MetadataInstruction::SetReservationList(args) => {
|
||||
msg!("Instruction: Set Reservation List");
|
||||
process_set_reservation_list(program_id, accounts, args.reservations)
|
||||
process_set_reservation_list(
|
||||
program_id,
|
||||
accounts,
|
||||
args.reservations,
|
||||
args.total_reservation_spots,
|
||||
)
|
||||
}
|
||||
MetadataInstruction::CreateReservationList => {
|
||||
msg!("Instruction: Create Reservation List");
|
||||
|
@ -541,6 +546,7 @@ pub fn process_set_reservation_list(
|
|||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
reservations: Vec<Reservation>,
|
||||
total_reservation_spots: Option<u64>,
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
|
@ -558,10 +564,6 @@ pub fn process_set_reservation_list(
|
|||
return Err(MetadataError::ReservationDoesNotExist.into());
|
||||
}
|
||||
|
||||
if reservations.len() > MAX_RESERVATIONS {
|
||||
return Err(MetadataError::BeyondMaxAddressSize.into());
|
||||
}
|
||||
|
||||
assert_derivation(
|
||||
program_id,
|
||||
reservation_list_info,
|
||||
|
@ -576,12 +578,12 @@ pub fn process_set_reservation_list(
|
|||
|
||||
let mut reservation_list = get_reservation_list(reservation_list_info)?;
|
||||
|
||||
if reservation_list.supply_snapshot().is_some() {
|
||||
if reservation_list.supply_snapshot().is_some() && total_reservation_spots.is_some() {
|
||||
return Err(MetadataError::ReservationAlreadyMade.into());
|
||||
}
|
||||
|
||||
let mut total_len: u64 = 0;
|
||||
let mut total_len_check: u64 = 0;
|
||||
let mut total_len: u64 = reservation_list.total_reservation_spots();
|
||||
let mut total_len_check: u64 = reservation_list.total_reservation_spots();
|
||||
|
||||
for reservation in &reservations {
|
||||
total_len = total_len
|
||||
|
@ -601,23 +603,34 @@ pub fn process_set_reservation_list(
|
|||
return Err(MetadataError::SpotMismatch.into());
|
||||
}
|
||||
|
||||
reservation_list.set_supply_snapshot(Some(master_edition.supply));
|
||||
reservation_list.set_reservations(reservations);
|
||||
msg!("Master edition {:?}", master_edition);
|
||||
msg!("Total new spots {:?}", total_len);
|
||||
master_edition.supply = master_edition
|
||||
.supply
|
||||
.checked_add(total_len as u64)
|
||||
.ok_or(MetadataError::NumericalOverflowError)?;
|
||||
reservation_list.add_reservations(reservations);
|
||||
|
||||
if let Some(max_supply) = master_edition.max_supply {
|
||||
if master_edition.supply > max_supply {
|
||||
return Err(MetadataError::ReservationBreachesMaximumSupply.into());
|
||||
if let Some(total) = total_reservation_spots {
|
||||
msg!("Total new spots allocated: {:?}", total);
|
||||
reservation_list.set_supply_snapshot(Some(master_edition.supply));
|
||||
reservation_list.set_total_reservation_spots(total);
|
||||
master_edition.supply = master_edition
|
||||
.supply
|
||||
.checked_add(total as u64)
|
||||
.ok_or(MetadataError::NumericalOverflowError)?;
|
||||
|
||||
if let Some(max_supply) = master_edition.max_supply {
|
||||
if master_edition.supply > max_supply {
|
||||
return Err(MetadataError::ReservationBreachesMaximumSupply.into());
|
||||
}
|
||||
}
|
||||
master_edition.serialize(&mut *master_edition_info.data.borrow_mut())?;
|
||||
}
|
||||
|
||||
if total_len > reservation_list.total_reservation_spots() {
|
||||
return Err(MetadataError::BeyondAlottedAddressSize.into());
|
||||
};
|
||||
|
||||
if reservation_list.reservations().len() > MAX_RESERVATIONS {
|
||||
return Err(MetadataError::BeyondMaxAddressSize.into());
|
||||
}
|
||||
|
||||
reservation_list.save(reservation_list_info)?;
|
||||
master_edition.serialize(&mut *master_edition_info.data.borrow_mut())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ pub const MAX_RESERVATIONS: usize = 200;
|
|||
pub const MAX_RESERVATION_LIST_V1_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 34 + 100;
|
||||
|
||||
// can hold up to 200 keys per reservation, note: the extra 8 is for number of elements in the vec
|
||||
pub const MAX_RESERVATION_LIST_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 48 + 100;
|
||||
pub const MAX_RESERVATION_LIST_SIZE: usize = 1 + 32 + 8 + 8 + MAX_RESERVATIONS * 48 + 8 + 92;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
|
||||
|
@ -170,9 +170,12 @@ pub trait ReservationList {
|
|||
fn master_edition(&self) -> Pubkey;
|
||||
fn supply_snapshot(&self) -> Option<u64>;
|
||||
fn reservations(&self) -> Vec<Reservation>;
|
||||
fn total_reservation_spots(&self) -> u64;
|
||||
fn set_master_edition(&mut self, key: Pubkey);
|
||||
fn set_supply_snapshot(&mut self, supply: Option<u64>);
|
||||
fn set_reservations(&mut self, reservations: Vec<Reservation>);
|
||||
fn add_reservations(&mut self, reservations: Vec<Reservation>);
|
||||
fn set_total_reservation_spots(&mut self, total_reservation_spots: u64);
|
||||
fn save(&self, account: &AccountInfo) -> ProgramResult;
|
||||
}
|
||||
|
||||
|
@ -199,6 +202,8 @@ pub struct ReservationListV2 {
|
|||
/// What supply counter was on master_edition when this reservation was created.
|
||||
pub supply_snapshot: Option<u64>,
|
||||
pub reservations: Vec<Reservation>,
|
||||
/// How many reservations there are going to be, given on first set_reservation call
|
||||
pub total_reservation_spots: u64,
|
||||
}
|
||||
|
||||
impl ReservationList for ReservationListV2 {
|
||||
|
@ -222,6 +227,10 @@ impl ReservationList for ReservationListV2 {
|
|||
self.supply_snapshot = supply;
|
||||
}
|
||||
|
||||
fn add_reservations(&mut self, mut reservations: Vec<Reservation>) {
|
||||
self.reservations.append(&mut reservations)
|
||||
}
|
||||
|
||||
fn set_reservations(&mut self, reservations: Vec<Reservation>) {
|
||||
self.reservations = reservations
|
||||
}
|
||||
|
@ -230,6 +239,14 @@ impl ReservationList for ReservationListV2 {
|
|||
self.serialize(&mut *account.data.borrow_mut())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn total_reservation_spots(&self) -> u64 {
|
||||
self.total_reservation_spots
|
||||
}
|
||||
|
||||
fn set_total_reservation_spots(&mut self, total_reservation_spots: u64) {
|
||||
self.total_reservation_spots = total_reservation_spots;
|
||||
}
|
||||
}
|
||||
|
||||
impl ReservationListV2 {
|
||||
|
@ -293,7 +310,7 @@ impl ReservationList for ReservationListV1 {
|
|||
self.supply_snapshot = supply;
|
||||
}
|
||||
|
||||
fn set_reservations(&mut self, reservations: Vec<Reservation>) {
|
||||
fn add_reservations(&mut self, reservations: Vec<Reservation>) {
|
||||
self.reservations = reservations
|
||||
.iter()
|
||||
.map(|r| ReservationV1 {
|
||||
|
@ -304,10 +321,20 @@ impl ReservationList for ReservationListV1 {
|
|||
.collect();
|
||||
}
|
||||
|
||||
fn set_reservations(&mut self, reservations: Vec<Reservation>) {
|
||||
self.add_reservations(reservations);
|
||||
}
|
||||
|
||||
fn save(&self, account: &AccountInfo) -> ProgramResult {
|
||||
self.serialize(&mut *account.data.borrow_mut())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn total_reservation_spots(&self) -> u64 {
|
||||
self.reservations.len() as u64
|
||||
}
|
||||
|
||||
fn set_total_reservation_spots(&mut self, _: u64) {}
|
||||
}
|
||||
|
||||
impl ReservationListV1 {
|
||||
|
|
|
@ -5,7 +5,9 @@ use {
|
|||
input_validators::{is_url, is_valid_pubkey, is_valid_signer},
|
||||
},
|
||||
solana_client::rpc_client::RpcClient,
|
||||
solana_program::{borsh::try_from_slice_unchecked, program_pack::Pack},
|
||||
solana_program::{
|
||||
account_info::AccountInfo, borsh::try_from_slice_unchecked, program_pack::Pack,
|
||||
},
|
||||
solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{read_keypair_file, Keypair, Signer},
|
||||
|
@ -22,7 +24,9 @@ use {
|
|||
mint_new_edition_from_master_edition_via_token, mint_printing_tokens,
|
||||
update_metadata_accounts,
|
||||
},
|
||||
state::{Data, Edition, Key, MasterEdition, Metadata, EDITION, PREFIX},
|
||||
state::{
|
||||
get_reservation_list, Data, Edition, Key, MasterEdition, Metadata, EDITION, PREFIX,
|
||||
},
|
||||
},
|
||||
std::str::FromStr,
|
||||
};
|
||||
|
@ -79,6 +83,24 @@ fn mint_coins(app_matches: &ArgMatches, payer: Keypair, client: RpcClient) {
|
|||
|
||||
println!("Minted {:?} tokens to {:?}.", amount, destination_key);
|
||||
}
|
||||
fn show_reservation_list(app_matches: &ArgMatches, _payer: Keypair, client: RpcClient) {
|
||||
let key = pubkey_of(app_matches, "key").unwrap();
|
||||
let mut res_data = client.get_account(&key).unwrap();
|
||||
let mut lamports = 0;
|
||||
let account_info = AccountInfo::new(
|
||||
&key,
|
||||
false,
|
||||
false,
|
||||
&mut lamports,
|
||||
&mut res_data.data,
|
||||
&res_data.owner,
|
||||
false,
|
||||
0,
|
||||
);
|
||||
|
||||
let res_list = get_reservation_list(&account_info).unwrap();
|
||||
println!("Res list {:?}", res_list.reservations());
|
||||
}
|
||||
|
||||
fn show(app_matches: &ArgMatches, _payer: Keypair, client: RpcClient) {
|
||||
let program_key = spl_token_metadata::id();
|
||||
|
@ -735,6 +757,18 @@ fn main() {
|
|||
.takes_value(true)
|
||||
.help("Metadata mint"),
|
||||
)
|
||||
).subcommand(
|
||||
SubCommand::with_name("show_reservation_list")
|
||||
.about("Show Reservation List")
|
||||
.arg(
|
||||
Arg::with_name("key")
|
||||
.long("key")
|
||||
.value_name("KEY")
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
.takes_value(true)
|
||||
.help("Account key of reservation list"),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("create_master_edition")
|
||||
|
@ -843,6 +877,9 @@ fn main() {
|
|||
("show", Some(arg_matches)) => {
|
||||
show(arg_matches, payer, client);
|
||||
}
|
||||
("show_reservation_list", Some(arg_matches)) => {
|
||||
show_reservation_list(arg_matches, payer, client);
|
||||
}
|
||||
("mint_coins", Some(arg_matches)) => {
|
||||
mint_coins(arg_matches, payer, client);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue