metaplex/rust/auction/program/src/processor.rs

784 lines
28 KiB
Rust

use crate::errors::AuctionError;
use arrayref::array_ref;
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
account_info::AccountInfo, borsh::try_from_slice_unchecked, clock::UnixTimestamp,
entrypoint::ProgramResult, hash::Hash, msg, program_error::ProgramError, pubkey::Pubkey,
};
use std::{cell::Ref, cmp, mem};
// Declare submodules, each contains a single handler for each instruction variant in the program.
pub mod cancel_bid;
pub mod claim_bid;
pub mod create_auction;
pub mod create_auction_v2;
pub mod end_auction;
pub mod place_bid;
pub mod set_authority;
pub mod start_auction;
// Re-export submodules handlers + associated types for other programs to consume.
pub use cancel_bid::*;
pub use claim_bid::*;
pub use create_auction::*;
pub use create_auction_v2::*;
pub use end_auction::*;
pub use place_bid::*;
pub use set_authority::*;
pub use start_auction::*;
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
input: &[u8],
) -> ProgramResult {
use crate::instruction::AuctionInstruction;
match AuctionInstruction::try_from_slice(input)? {
AuctionInstruction::CancelBid(args) => cancel_bid(program_id, accounts, args),
AuctionInstruction::ClaimBid(args) => claim_bid(program_id, accounts, args),
AuctionInstruction::CreateAuction(args) => {
create_auction(program_id, accounts, args, None, None)
}
AuctionInstruction::CreateAuctionV2(args) => create_auction_v2(program_id, accounts, args),
AuctionInstruction::EndAuction(args) => end_auction(program_id, accounts, args),
AuctionInstruction::PlaceBid(args) => place_bid(program_id, accounts, args),
AuctionInstruction::SetAuthority => set_authority(program_id, accounts),
AuctionInstruction::StartAuction(args) => start_auction(program_id, accounts, args),
}
}
/// Structure with pricing floor data.
#[repr(C)]
#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Debug)]
pub enum PriceFloor {
/// Due to borsh on the front end disallowing different arguments in enums, we have to make sure data is
/// same size across all three
/// No price floor, any bid is valid.
None([u8; 32]),
/// Explicit minimum price, any bid below this is rejected.
MinimumPrice([u64; 4]),
/// Hidden minimum price, revealed at the end of the auction.
BlindedPrice(Hash),
}
// The two extra 8's are present, one 8 is for the Vec's amount of elements and one is for the max
// usize in bid state.
// NOTE: New research suggests u32s are used for vecs in borsh, not u64s, so the first extra 8 should be a 4
// but for legacy reasons we leave it behind.
pub const BASE_AUCTION_DATA_SIZE: usize = 32 + 32 + 9 + 9 + 9 + 9 + 1 + 32 + 1 + 8 + 8 + 8;
pub const BID_LENGTH: usize = 32 + 8;
#[repr(C)]
#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Debug)]
pub struct AuctionData {
/// Pubkey of the authority with permission to modify this auction.
pub authority: Pubkey,
/// Pubkey of the resource being bid on.
/// TODO try to bring this back some day. Had to remove this due to a stack access violation bug
/// interactin that happens in metaplex during redemptions due to some low level rust error
/// that happens when AuctionData has too many fields. This field was the least used.
///pub resource: Pubkey,
/// Token mint for the SPL token being used to bid
pub token_mint: Pubkey,
/// The time the last bid was placed, used to keep track of auction timing.
pub last_bid: Option<UnixTimestamp>,
/// Slot time the auction was officially ended by.
pub ended_at: Option<UnixTimestamp>,
/// End time is the cut-off point that the auction is forced to end by.
pub end_auction_at: Option<UnixTimestamp>,
/// Gap time is the amount of time in slots after the previous bid at which the auction ends.
pub end_auction_gap: Option<UnixTimestamp>,
/// Minimum price for any bid to meet.
pub price_floor: PriceFloor,
/// The state the auction is in, whether it has started or ended.
pub state: AuctionState,
/// Auction Bids, each user may have one bid open at a time.
pub bid_state: BidState,
}
// Alias for auction name.
pub type AuctionName = [u8; 32];
pub const MAX_AUCTION_DATA_EXTENDED_SIZE: usize = 8 + 9 + 2 + 9 + 191;
// Further storage for more fields. Would like to store more on the main data but due
// to a borsh issue that causes more added fields to inflict "Access violation" errors
// during redemption in main Metaplex app for no reason, we had to add this nasty PDA.
#[repr(C)]
#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Debug)]
pub struct AuctionDataExtended {
/// Total uncancelled bids
pub total_uncancelled_bids: u64,
// Unimplemented fields
/// Tick size
pub tick_size: Option<u64>,
/// gap_tick_size_percentage - two decimal points
pub gap_tick_size_percentage: Option<u8>,
/// Instant sale price
pub instant_sale_price: Option<u64>,
/// Auction name
pub name: Option<AuctionName>,
}
impl AuctionDataExtended {
pub fn from_account_info(a: &AccountInfo) -> Result<AuctionDataExtended, ProgramError> {
if a.data_len() != MAX_AUCTION_DATA_EXTENDED_SIZE {
return Err(AuctionError::DataTypeMismatch.into());
}
let auction_extended: AuctionDataExtended = try_from_slice_unchecked(&a.data.borrow_mut())?;
Ok(auction_extended)
}
pub fn get_instant_sale_price<'a>(data: &'a Ref<'a, &'a mut [u8]>) -> Option<u64> {
if let Some(idx) = Self::find_instant_sale_beginning(data) {
Some(u64::from_le_bytes(*array_ref![data, idx, 8]))
} else {
None
}
}
fn find_instant_sale_beginning<'a>(data: &'a Ref<'a, &'a mut [u8]>) -> Option<usize> {
// total_uncancelled_bids + tick_size Option
let mut instant_sale_beginning = 8;
// gaps for tick_size and gap_tick_size_percentage
let gaps = [9, 2];
for gap in gaps.iter() {
if data[instant_sale_beginning] == 1 {
instant_sale_beginning += gap;
} else {
instant_sale_beginning += 1;
}
}
// check if instant_sale_price has some value
if data[instant_sale_beginning] == 1 {
Some(instant_sale_beginning + 1)
} else {
None
}
}
}
impl AuctionData {
// Cheap methods to get at AuctionData without supremely expensive borsh deserialization calls.
pub fn get_token_mint(a: &AccountInfo) -> Pubkey {
let data = a.data.borrow();
let token_mint_data = array_ref![data, 32, 32];
Pubkey::new_from_array(*token_mint_data)
}
pub fn get_state(a: &AccountInfo) -> Result<AuctionState, ProgramError> {
// Remove the +1 to get rid of first byte of first bid, then -4 to subtract the u32 that is vec size of bids,
// now we're back at the beginning of the u32, -1 again to get to state
let bid_state_beginning = AuctionData::find_bid_state_beginning(a) - 1 - 4 - 1;
match a.data.borrow()[bid_state_beginning] {
0 => Ok(AuctionState::Created),
1 => Ok(AuctionState::Started),
2 => Ok(AuctionState::Ended),
_ => Err(ProgramError::InvalidAccountData),
}
}
pub fn get_num_winners(a: &AccountInfo) -> usize {
let (bid_state_beginning, num_elements, max) = AuctionData::get_vec_info(a);
std::cmp::min(num_elements, max)
}
fn find_bid_state_beginning(a: &AccountInfo) -> usize {
let data = a.data.borrow();
let mut bid_state_beginning = 32 + 32;
for i in 0..4 {
// One for each unix timestamp
if data[bid_state_beginning] == 1 {
bid_state_beginning += 9
} else {
bid_state_beginning += 1;
}
}
// Finally add price floor (enum + hash) and state, then the u32,
// then add 1 to position at the beginning of first bid.
bid_state_beginning += 1 + 32 + 1 + 4 + 1;
return bid_state_beginning;
}
fn get_vec_info(a: &AccountInfo) -> (usize, usize, usize) {
let bid_state_beginning = AuctionData::find_bid_state_beginning(a);
let data = a.data.borrow();
let num_elements_data = array_ref![data, bid_state_beginning - 4, 4];
let num_elements = u32::from_le_bytes(*num_elements_data) as usize;
let max_data = array_ref![data, bid_state_beginning + BID_LENGTH * num_elements, 8];
let max = u64::from_le_bytes(*max_data) as usize;
(bid_state_beginning, num_elements, max)
}
pub fn get_is_winner(a: &AccountInfo, key: &Pubkey) -> Option<usize> {
let bid_state_beginning = AuctionData::find_bid_state_beginning(a);
let data = a.data.borrow();
let as_bytes = key.to_bytes();
let (bid_state_beginning, num_elements, max) = AuctionData::get_vec_info(a);
for idx in 0..std::cmp::min(num_elements, max) {
match AuctionData::get_winner_at_inner(
&a.data.borrow(),
idx,
bid_state_beginning,
num_elements,
max,
) {
Some(bid_key) => {
// why deserialize the entire key to compare the two with a short circuit comparison
// when we can compare them immediately?
let mut matching = true;
for bid_key_idx in 0..32 {
if bid_key[bid_key_idx] != as_bytes[bid_key_idx] {
matching = false;
break;
}
}
if matching {
return Some(idx as usize);
}
}
None => return None,
}
}
None
}
pub fn get_winner_at(a: &AccountInfo, idx: usize) -> Option<Pubkey> {
let (bid_state_beginning, num_elements, max) = AuctionData::get_vec_info(a);
match AuctionData::get_winner_at_inner(
&a.data.borrow(),
idx,
bid_state_beginning,
num_elements,
max,
) {
Some(bid_key) => Some(Pubkey::new_from_array(*bid_key)),
None => None,
}
}
fn get_winner_at_inner<'a>(
data: &'a Ref<'a, &'a mut [u8]>,
idx: usize,
bid_state_beginning: usize,
num_elements: usize,
max: usize,
) -> Option<&'a [u8; 32]> {
if idx + 1 > num_elements || idx + 1 > max {
return None;
}
Some(array_ref![
data,
bid_state_beginning + (num_elements - idx - 1) * BID_LENGTH,
32
])
}
pub fn get_winner_bid_amount_at(a: &AccountInfo, idx: usize) -> Option<u64> {
let (bid_state_beginning, num_elements, max) = AuctionData::get_vec_info(a);
match AuctionData::get_winner_bid_amount_at_inner(
&a.data.borrow(),
idx,
bid_state_beginning,
num_elements,
max,
) {
Some(bid_amount) => Some(bid_amount),
None => None,
}
}
fn get_winner_bid_amount_at_inner<'a>(
data: &'a Ref<'a, &'a mut [u8]>,
idx: usize,
bid_state_beginning: usize,
num_elements: usize,
max: usize,
) -> Option<u64> {
if idx + 1 > num_elements || idx + 1 > max {
return None;
}
Some(u64::from_le_bytes(*array_ref![
data,
bid_state_beginning + (num_elements - idx - 1) * BID_LENGTH + 32,
8
]))
}
pub fn from_account_info(a: &AccountInfo) -> Result<AuctionData, ProgramError> {
if (a.data_len() - BASE_AUCTION_DATA_SIZE) % mem::size_of::<Bid>() != 0 {
return Err(AuctionError::DataTypeMismatch.into());
}
let auction: AuctionData = try_from_slice_unchecked(&a.data.borrow_mut())?;
Ok(auction)
}
pub fn ended(&self, now: UnixTimestamp) -> Result<bool, ProgramError> {
// If there is an end time specified, handle conditions.
return match (self.ended_at, self.end_auction_gap) {
// NOTE if changing this, change in auction.ts on front end as well where logic duplicates.
// Both end and gap present, means a bid can still be placed post-auction if it is
// within the gap time.
(Some(end), Some(gap)) => {
// Check if the bid is within the gap between the last bidder.
if let Some(last) = self.last_bid {
let next_bid_time = last
.checked_add(gap)
.ok_or(AuctionError::NumericalOverflowError)?;
Ok(now > end && now > next_bid_time)
} else {
Ok(now > end)
}
}
// Simply whether now has passed the end.
(Some(end), None) => Ok(now > end),
// No other end conditions.
_ => Ok(false),
};
}
pub fn is_winner(&self, key: &Pubkey) -> Option<usize> {
let minimum = match self.price_floor {
PriceFloor::MinimumPrice(min) => min[0],
_ => 0,
};
self.bid_state.is_winner(key, minimum)
}
pub fn num_winners(&self) -> u64 {
self.bid_state.num_winners()
}
pub fn num_possible_winners(&self) -> u64 {
self.bid_state.num_possible_winners()
}
pub fn winner_at(&self, idx: usize) -> Option<Pubkey> {
self.bid_state.winner_at(idx)
}
pub fn consider_instant_bid(&mut self, instant_sale_price: Option<u64>) {
// Check if all the lots were sold with instant_sale_price
if let Some(price) = instant_sale_price {
if self.bid_state.has_instant_bid(price) {
msg!("All the lots were sold with instant_sale_price, auction is ended");
self.state = AuctionState::Ended;
}
}
}
pub fn place_bid(
&mut self,
bid: Bid,
tick_size: Option<u64>,
gap_tick_size_percentage: Option<u8>,
now: UnixTimestamp,
instant_sale_price: Option<u64>,
) -> Result<(), ProgramError> {
let gap_val = match self.ended_at {
Some(end) => {
// We use the actual gap tick size perc if we're in gap window,
// otherwise we pass in none so the logic isnt used
if now > end {
gap_tick_size_percentage
} else {
None
}
}
None => None,
};
let minimum = match self.price_floor {
PriceFloor::MinimumPrice(min) => min[0],
_ => 0,
};
self.bid_state.place_bid(
bid,
tick_size,
gap_val,
minimum,
instant_sale_price,
&mut self.state,
)?;
self.consider_instant_bid(instant_sale_price);
Ok(())
}
}
/// Define valid auction state transitions.
#[repr(C)]
#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Debug)]
pub enum AuctionState {
Created,
Started,
Ended,
}
impl AuctionState {
pub fn create() -> Self {
AuctionState::Created
}
#[inline(always)]
pub fn start(self) -> Result<Self, ProgramError> {
match self {
AuctionState::Created => Ok(AuctionState::Started),
_ => Err(AuctionError::AuctionTransitionInvalid.into()),
}
}
#[inline(always)]
pub fn end(self) -> Result<Self, ProgramError> {
match self {
AuctionState::Started => Ok(AuctionState::Ended),
AuctionState::Created => Ok(AuctionState::Ended),
_ => Err(AuctionError::AuctionTransitionInvalid.into()),
}
}
}
/// Bids associate a bidding key with an amount bid.
#[repr(C)]
#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Debug)]
pub struct Bid(pub Pubkey, pub u64);
/// BidState tracks the running state of an auction, each variant represents a different kind of
/// auction being run.
#[repr(C)]
#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Debug)]
pub enum BidState {
EnglishAuction { bids: Vec<Bid>, max: usize },
OpenEdition { bids: Vec<Bid>, max: usize },
}
/// Bidding Implementations.
///
/// English Auction: this stores only the current winning bids in the auction, pruning cancelled
/// and lost bids over time.
///
/// Open Edition: All bids are accepted, cancellations return money to the bidder and always
/// succeed.
impl BidState {
pub fn new_english(n: usize) -> Self {
BidState::EnglishAuction {
bids: vec![],
max: n,
}
}
pub fn new_open_edition() -> Self {
BidState::OpenEdition {
bids: vec![],
max: 0,
}
}
pub fn max_array_size_for(n: usize) -> usize {
let mut real_max = n;
if real_max < 8 {
real_max = 8;
} else {
real_max = 2 * real_max
}
real_max
}
fn assert_valid_tick_size_bid(bid: &Bid, tick_size: Option<u64>) -> ProgramResult {
if let Some(tick) = tick_size {
if bid.1.checked_rem(tick) != Some(0) {
msg!(
"This bid {:?} is not a multiple of tick size {:?}, throw it out.",
bid.1,
tick_size
);
return Err(AuctionError::BidMustBeMultipleOfTickSize.into());
}
} else {
msg!("No tick size on this auction")
}
Ok(())
}
fn assert_valid_gap_insertion(
gap_tick: u8,
beaten_bid: &Bid,
beating_bid: &Bid,
) -> ProgramResult {
// Use u128 to avoid potential overflow due to temporary mult of 100x since
// we haven't divided yet.
let mut minimum_bid_amount: u128 = (beaten_bid.1 as u128)
.checked_mul((100 + gap_tick) as u128)
.ok_or(AuctionError::NumericalOverflowError)?;
minimum_bid_amount = minimum_bid_amount
.checked_div(100u128)
.ok_or(AuctionError::NumericalOverflowError)?;
if minimum_bid_amount > beating_bid.1 as u128 {
msg!("Rejecting inserting this bid due to gap tick size of {:?} which causes min bid of {:?} from {:?} which is the bid it is trying to beat", gap_tick, minimum_bid_amount.to_string(), beaten_bid.1);
return Err(AuctionError::GapBetweenBidsTooSmall.into());
}
Ok(())
}
/// Push a new bid into the state, this succeeds only if the bid is larger than the current top
/// winner stored. Crappy list information to start with.
pub fn place_bid(
&mut self,
bid: Bid,
tick_size: Option<u64>,
gap_tick_size_percentage: Option<u8>,
minimum: u64,
instant_sale_price: Option<u64>,
auction_state: &mut AuctionState,
) -> Result<(), ProgramError> {
msg!("Placing bid {:?}", &bid.1.to_string());
BidState::assert_valid_tick_size_bid(&bid, tick_size)?;
if bid.1 < minimum {
return Err(AuctionError::BidTooSmall.into());
}
match self {
// In a capped auction, track the limited number of winners.
BidState::EnglishAuction { ref mut bids, max } => {
match bids.last() {
Some(top) => {
msg!("Looking to go over the loop, but check tick size first");
for i in (0..bids.len()).rev() {
msg!("Comparison of {:?} and {:?} for {:?}", bids[i].1, bid.1, i);
if bids[i].1 < bid.1 {
if let Some(gap_tick) = gap_tick_size_percentage {
BidState::assert_valid_gap_insertion(gap_tick, &bids[i], &bid)?
}
msg!("Ok we can do an insert");
if i + 1 < bids.len() {
msg!("Doing a normal insert");
bids.insert(i + 1, bid);
} else {
msg!("Doing an on the end insert");
bids.push(bid)
}
break;
} else if bids[i].1 == bid.1 {
if let Some(gap_tick) = gap_tick_size_percentage {
if gap_tick > 0 {
msg!("Rejecting same-bid insert due to gap tick size of {:?}", gap_tick);
return Err(AuctionError::GapBetweenBidsTooSmall.into());
}
}
msg!("Ok we can do an equivalent insert");
if i == 0 {
msg!("Doing a normal insert");
bids.insert(0, bid);
break;
} else {
if bids[i - 1].1 != bids[i].1 {
msg!("Doing an insert just before");
bids.insert(i, bid);
break;
}
msg!("More duplicates ahead...")
}
} else if i == 0 {
msg!("Inserting at 0");
bids.insert(0, bid);
break;
}
}
let max_size = BidState::max_array_size_for(*max);
if bids.len() > max_size {
bids.remove(0);
}
Ok(())
}
_ => {
msg!("Pushing bid onto stack");
bids.push(bid);
Ok(())
}
}
}
// In an open auction, bidding simply succeeds.
BidState::OpenEdition { bids, max } => Ok(()),
}
}
/// Cancels a bid, if the bid was a winning bid it is removed, if the bid is invalid the
/// function simple no-ops.
pub fn cancel_bid(&mut self, key: Pubkey) -> Result<(), ProgramError> {
match self {
BidState::EnglishAuction { ref mut bids, max } => {
bids.retain(|b| b.0 != key);
Ok(())
}
// In an open auction, cancelling simply succeeds. It's up to the manager of an auction
// to decide what to do with open edition bids.
BidState::OpenEdition { bids, max } => Ok(()),
}
}
pub fn amount(&self, index: usize) -> u64 {
match self {
BidState::EnglishAuction { bids, max } => {
if index >= 0 as usize && index < bids.len() {
return bids[bids.len() - index - 1].1;
} else {
return 0;
}
}
BidState::OpenEdition { bids, max } => 0,
}
}
/// Check if a pubkey is currently a winner and return winner #1 as index 0 to outside world.
pub fn is_winner(&self, key: &Pubkey, min: u64) -> Option<usize> {
// NOTE if changing this, change in auction.ts on front end as well where logic duplicates.
match self {
// Presense in the winner list is enough to check win state.
BidState::EnglishAuction { bids, max } => {
match bids.iter().position(|bid| &bid.0 == key && bid.1 >= min) {
Some(val) => {
let zero_based_index = bids.len() - val - 1;
if zero_based_index < *max {
Some(zero_based_index)
} else {
None
}
}
None => None,
}
}
// There are no winners in an open edition, it is up to the auction manager to decide
// what to do with open edition bids.
BidState::OpenEdition { bids, max } => None,
}
}
pub fn num_winners(&self) -> u64 {
match self {
BidState::EnglishAuction { bids, max } => cmp::min(bids.len(), *max) as u64,
BidState::OpenEdition { bids, max } => 0,
}
}
pub fn num_possible_winners(&self) -> u64 {
match self {
BidState::EnglishAuction { bids, max } => *max as u64,
BidState::OpenEdition { bids, max } => 0,
}
}
/// Idea is to present #1 winner as index 0 to outside world with this method
pub fn winner_at(&self, index: usize) -> Option<Pubkey> {
match self {
BidState::EnglishAuction { bids, max } => {
if index < *max && index < bids.len() {
let bid = &bids[bids.len() - index - 1];
Some(bids[bids.len() - index - 1].0)
} else {
None
}
}
BidState::OpenEdition { bids, max } => None,
}
}
pub fn has_instant_bid(&self, instant_sale_amount: u64) -> bool {
match self {
// In a capped auction, track the limited number of winners.
BidState::EnglishAuction { bids, max } | BidState::OpenEdition { bids, max } => {
// bids.len() - max = index of the last winner bid
bids.len() >= *max && bids[bids.len() - *max].1 >= instant_sale_amount
}
_ => false,
}
}
}
#[repr(C)]
#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Debug)]
pub enum WinnerLimit {
Unlimited(usize),
Capped(usize),
}
pub const BIDDER_METADATA_LEN: usize = 32 + 32 + 8 + 8 + 1;
/// Models a set of metadata for a bidder, meant to be stored in a PDA. This allows looking up
/// information about a bidder regardless of if they have won, lost or cancelled.
#[repr(C)]
#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Debug)]
pub struct BidderMetadata {
// Relationship with the bidder who's metadata this covers.
pub bidder_pubkey: Pubkey,
// Relationship with the auction this bid was placed on.
pub auction_pubkey: Pubkey,
// Amount that the user bid.
pub last_bid: u64,
// Tracks the last time this user bid.
pub last_bid_timestamp: UnixTimestamp,
// Whether the last bid the user made was cancelled. This should also be enough to know if the
// user is a winner, as if cancelled it implies previous bids were also cancelled.
pub cancelled: bool,
}
impl BidderMetadata {
pub fn from_account_info(a: &AccountInfo) -> Result<BidderMetadata, ProgramError> {
if a.data_len() != BIDDER_METADATA_LEN {
return Err(AuctionError::DataTypeMismatch.into());
}
let bidder_meta: BidderMetadata = try_from_slice_unchecked(&a.data.borrow_mut())?;
Ok(bidder_meta)
}
}
#[repr(C)]
#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq)]
pub struct BidderPot {
/// Points at actual pot that is a token account
pub bidder_pot: Pubkey,
/// Originating bidder account
pub bidder_act: Pubkey,
/// Auction account
pub auction_act: Pubkey,
/// emptied or not
pub emptied: bool,
}
impl BidderPot {
pub fn from_account_info(a: &AccountInfo) -> Result<BidderPot, ProgramError> {
if a.data_len() != mem::size_of::<BidderPot>() {
return Err(AuctionError::DataTypeMismatch.into());
}
let bidder_pot: BidderPot = try_from_slice_unchecked(&a.data.borrow_mut())?;
Ok(bidder_pot)
}
}