I feel really guilty about this, but this hack was required to scale to 200 reservations in the current reservation system. We will soon deprecate in favor of an authority based system, but didnt want to leave people behind in the mean time. (#80)

This commit is contained in:
Jordan Prince 2021-06-27 14:17:28 -05:00 committed by GitHub
parent 566c52ac40
commit 7bfbf0f0af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 164 additions and 90 deletions

View File

@ -28,16 +28,10 @@ fn set_reservation_list_wrapper<'a>(
auction_manager_info: &AccountInfo<'a>,
signer_seeds: &[&[u8]],
reservations: Vec<Reservation>,
first_push: bool,
total_reservation_spots: u64,
total_reservation_spots: Option<u64>,
offset: u64,
total_spot_offset: 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,
@ -45,7 +39,9 @@ fn set_reservation_list_wrapper<'a>(
*reservation_list_info.key,
*auction_manager_info.key,
reservations,
total_reservation_spot_opt,
total_reservation_spots,
offset,
total_spot_offset,
),
&[
master_edition_info.clone(),
@ -58,12 +54,27 @@ fn set_reservation_list_wrapper<'a>(
Ok(())
}
pub fn calc_spots(
winning_config_item: &WinningConfigItem,
auction_manager: &AuctionManager,
n: usize,
) -> u64 {
auction_manager.settings.winning_configs[n]
.items
.iter()
.filter(|i| i.safety_deposit_box_index == winning_config_item.safety_deposit_box_index)
.map(|i| i.amount as u64)
.sum()
}
#[allow(clippy::too_many_arguments)]
pub fn reserve_list_if_needed<'a>(
program_id: &'a Pubkey,
auction_manager: &AuctionManager,
auction: &AuctionData,
winning_config_item: &WinningConfigItem,
winning_index: usize,
bidder_info: &AccountInfo<'a>,
master_edition_info: &AccountInfo<'a>,
reservation_list_info: &AccountInfo<'a>,
auction_manager_info: &AccountInfo<'a>,
@ -71,78 +82,56 @@ pub fn reserve_list_if_needed<'a>(
) -> ProgramResult {
let reservation_list = get_reservation_list(reservation_list_info)?;
if reservation_list.supply_snapshot().is_none() {
let mut reservations: Vec<Reservation> = vec![];
let total_reservation_spot_opt: Option<u64>;
// Auction specifically does not expose internal state workings as it may change someday,
// but it does expose a point get-winner-at-index method. Right now this is just array access
// 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) => {
let spots: u64 = auction_manager.settings.winning_configs[n]
.items
.iter()
.filter(|i| {
i.safety_deposit_box_index
== winning_config_item.safety_deposit_box_index
})
.map(|i| i.amount as u64)
.sum();
total_reservation_spots = total_reservation_spots
// Auction specifically does not expose internal state workings as it may change someday,
// but it does expose a point get-winner-at-index method. Right now this is just array access
// 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;
let mut total_spot_offset: u64 = 0;
for n in 0..auction_manager.settings.winning_configs.len() {
match auction.winner_at(n) {
Some(_) => {
let spots: u64 = calc_spots(winning_config_item, auction_manager, n);
total_reservation_spots = total_reservation_spots
.checked_add(spots)
.ok_or(MetaplexError::NumericalOverflowError)?;
if n < winning_index {
total_spot_offset = total_spot_offset
.checked_add(spots)
.ok_or(MetaplexError::NumericalOverflowError)?;
reservations.push(Reservation {
address,
// Select all items in a winning config matching the same safety deposit box
// as the one being redeemed here (likely only one)
// and then sum them to get the total spots to reserve for this winner
spots_remaining: spots,
total_spots: spots,
});
}
None => break,
}
}
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(20) == 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,
)?;
None => break,
}
}
if reservation_list.supply_snapshot().is_none() {
total_reservation_spot_opt = Some(total_reservation_spots)
} else {
total_reservation_spot_opt = None
}
let my_spots: u64 = calc_spots(winning_config_item, auction_manager, winning_index);
set_reservation_list_wrapper(
program_id,
master_edition_info,
reservation_list_info,
auction_manager_info,
signer_seeds,
vec![Reservation {
address: *bidder_info.key,
spots_remaining: my_spots,
total_spots: my_spots,
}],
total_reservation_spot_opt,
winning_index as u64,
total_spot_offset,
)?;
Ok(())
}
pub fn process_redeem_bid<'a>(
@ -244,6 +233,8 @@ pub fn process_redeem_bid<'a>(
&auction_manager,
&auction,
&winning_config_item,
winning_index,
bidder_info,
master_edition_info,
reservation_list_info,
auction_manager_info,

View File

@ -274,6 +274,10 @@ pub enum MetadataError {
/// The reservation has only been partially alotted
#[error("The reservation has only been partially alotted")]
ReservationNotComplete,
/// You cannot splice over an existing reservation!
#[error("You cannot splice over an existing reservation!")]
TriedToReplaceAnExistingReservation,
}
impl PrintProgramError for MetadataError {

View File

@ -47,6 +47,13 @@ pub struct SetReservationListArgs {
pub reservations: Vec<Reservation>,
/// should only be present on the very first call to set reservation list.
pub total_reservation_spots: Option<u64>,
/// Where in the reservation list you want to insert this slice of reservations
pub offset: u64,
/// What the total spot offset is in the reservation list from the beginning to your slice of reservations.
/// So if is going to be 4 total editions eventually reserved between your slice and the beginning of the array,
/// split between 2 reservation entries, the offset variable above would be "2" since you start at entry 2 in 0 indexed array
/// (first 2 taking 0 and 1) and because they each have 2 spots taken, this variable would be 4.
pub total_spot_offset: u64,
}
/// Instructions supported by the Metadata program.
@ -126,13 +133,13 @@ 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,
/// NOTE: If you have more than 20 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
/// 2. `[signer]` The resource you tied the reservation list too
SetReservationList(SetReservationListArgs),
/// Create an empty reservation list for a resource who can come back later as a signer and fill the reservation list
@ -370,6 +377,8 @@ pub fn set_reservation_list(
resource: Pubkey,
reservations: Vec<Reservation>,
total_reservation_spots: Option<u64>,
offset: u64,
total_spot_offset: u64,
) -> Instruction {
Instruction {
program_id,
@ -381,6 +390,8 @@ pub fn set_reservation_list(
data: MetadataInstruction::SetReservationList(SetReservationListArgs {
reservations,
total_reservation_spots,
offset,
total_spot_offset,
})
.try_to_vec()
.unwrap(),

View File

@ -75,6 +75,8 @@ pub fn process_instruction(
accounts,
args.reservations,
args.total_reservation_spots,
args.offset,
args.total_spot_offset,
)
}
MetadataInstruction::CreateReservationList => {
@ -547,6 +549,8 @@ pub fn process_set_reservation_list(
accounts: &[AccountInfo],
reservations: Vec<Reservation>,
total_reservation_spots: Option<u64>,
offset: u64,
total_spot_offset: u64,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
@ -600,7 +604,7 @@ pub fn process_set_reservation_list(
}
reservation_list.set_current_reservation_spots(total_len);
reservation_list.add_reservations(reservations);
reservation_list.add_reservations(reservations, offset, total_spot_offset)?;
if let Some(total) = total_reservation_spots {
reservation_list.set_supply_snapshot(Some(master_edition.supply));

View File

@ -174,8 +174,13 @@ pub trait ReservationList {
fn current_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_reservations(&mut self, reservations: Vec<Reservation>) -> ProgramResult;
fn add_reservations(
&mut self,
reservations: Vec<Reservation>,
offset: u64,
total_spot_offset: u64,
) -> ProgramResult;
fn set_total_reservation_spots(&mut self, total_reservation_spots: u64);
fn set_current_reservation_spots(&mut self, current_reservation_spots: u64);
fn save(&self, account: &AccountInfo) -> ProgramResult;
@ -231,12 +236,52 @@ impl ReservationList for ReservationListV2 {
self.supply_snapshot = supply;
}
fn add_reservations(&mut self, mut reservations: Vec<Reservation>) {
self.reservations.append(&mut reservations)
fn add_reservations(
&mut self,
mut reservations: Vec<Reservation>,
offset: u64,
total_spot_offset: u64,
) -> ProgramResult {
let usize_offset = offset as usize;
while self.reservations.len() < usize_offset {
self.reservations.push(Reservation {
address: solana_program::system_program::id(),
spots_remaining: 0,
total_spots: 0,
})
}
if self.reservations.len() > usize_offset {
let removed_elements: Vec<Reservation> = self
.reservations
.splice(
usize_offset..usize_offset + reservations.len(),
reservations,
)
.collect();
let existing_res = removed_elements
.iter()
.find(|r| r.address != solana_program::system_program::id());
if existing_res.is_some() {
return Err(MetadataError::TriedToReplaceAnExistingReservation.into());
}
} else {
self.reservations.append(&mut reservations)
}
if usize_offset != 0
&& self.reservations[usize_offset - 1].address == solana_program::system_program::id()
{
// This becomes an anchor then for calculations... put total spot offset in here.
self.reservations[usize_offset - 1].spots_remaining = total_spot_offset;
self.reservations[usize_offset - 1].total_spots = total_spot_offset;
}
Ok(())
}
fn set_reservations(&mut self, reservations: Vec<Reservation>) {
self.reservations = reservations
fn set_reservations(&mut self, reservations: Vec<Reservation>) -> ProgramResult {
self.reservations = reservations;
Ok(())
}
fn save(&self, account: &AccountInfo) -> ProgramResult {
@ -322,7 +367,12 @@ impl ReservationList for ReservationListV1 {
self.supply_snapshot = supply;
}
fn add_reservations(&mut self, reservations: Vec<Reservation>) {
fn add_reservations(
&mut self,
reservations: Vec<Reservation>,
_: u64,
_: u64,
) -> ProgramResult {
self.reservations = reservations
.iter()
.map(|r| ReservationV1 {
@ -331,10 +381,13 @@ impl ReservationList for ReservationListV1 {
total_spots: r.total_spots as u8,
})
.collect();
Ok(())
}
fn set_reservations(&mut self, reservations: Vec<Reservation>) {
self.add_reservations(reservations);
fn set_reservations(&mut self, reservations: Vec<Reservation>) -> ProgramResult {
self.add_reservations(reservations, 0, 0)?;
Ok(())
}
fn save(&self, account: &AccountInfo) -> ProgramResult {

View File

@ -424,6 +424,7 @@ pub fn mint_limited_edition<'a>(
let mut reservations = reservation_list.reservations();
for i in 0..reservations.len() {
let mut reservation = &mut reservations[i];
if reservation.address == *mint_authority_info.key {
offset = Some(
prev_total_offsets
@ -436,13 +437,21 @@ pub fn mint_limited_edition<'a>(
.checked_sub(1)
.ok_or(MetadataError::NumericalOverflowError)?;
reservation_list.set_reservations(reservations);
reservation_list.set_reservations(reservations)?;
reservation_list.save(account)?;
break;
}
prev_total_offsets = prev_total_offsets
.checked_add(reservation.total_spots)
.ok_or(MetadataError::NumericalOverflowError)?;
if reservation.address == solana_program::system_program::id() {
// This is an anchor point in the array...it means we reset our math to
// this offset because we may be missing information in between this point and
// the points before it.
prev_total_offsets = reservation.total_spots;
} else {
prev_total_offsets = prev_total_offsets
.checked_add(reservation.total_spots)
.ok_or(MetadataError::NumericalOverflowError)?;
}
}
match offset {

View File

@ -104,6 +104,8 @@ fn show_reservation_list(app_matches: &ArgMatches, _payer: Keypair, client: RpcC
"current res spots: {:?}",
res_list.current_reservation_spots()
);
println!("total res spots: {:?}", res_list.total_reservation_spots());
println!("supply snapshot: {:?}", res_list.supply_snapshot());
}
fn show(app_matches: &ArgMatches, _payer: Keypair, client: RpcClient) {