reservation list fix for scaling (#70)

This commit is contained in:
Jordan Prince 2021-06-24 19:07:29 -05:00 committed by GitHub
parent eab0796c35
commit 5a4086738b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1091 additions and 159 deletions

View File

@ -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'],
],
},
],

1005
rust/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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(())

View File

@ -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 {

View File

@ -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(),
}
}

View File

@ -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(())
}

View File

@ -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 {

View File

@ -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);
}