Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

orbital auction #24

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open

orbital auction #24

wants to merge 20 commits into from

Conversation

bekauz
Copy link
Collaborator

@bekauz bekauz commented Oct 3, 2024

closes #19

introduces the core logic of orbital auction:

  • processing user intents into an orderbook
  • processing the orderbook into round-based auction batches
  • allowing solvers to post bonds which allows them to bid on auctions
  • slash solvers who failed to fulfill their bids (mocked for now)
  • auction phase logic

this pr does not include integration with orbital-core, so it is still rough around the edges and contains some unimplemented!() calls. that being said, the missing pieces are quite isolated from the core auction mechanics and this pr is already big enough so I think it makes sense to get some feedback on this sooner rather than later.

@bekauz bekauz marked this pull request as ready for review October 4, 2024 12:56
/// - filling: window where the auction is finalized and orders are matched
/// - cleanup: window where the auction is reset and the next round is prepared
// TODO: validate that all durations are passed in seconds.
// or just drop Duration altogether and deal in seconds?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why in seconds and not blocks ?

pub fn advance(&self, storage: &mut dyn Storage) -> StdResult<Self> {
let auction_config = AUCTION_CONFIG.load(storage)?;

let next_id = self.id + Uint64::one();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use checked arithmetics.

pub start_expiration: Expiration,
pub auction_expiration: Expiration,
pub filling_expiration: Expiration,
pub cleanup_expiration: Expiration,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the naming confusing .. I understand the type is named Expiration, but adding it as a prefix in member names still involve some added brain processing; how about auction_start, auction_end, filling_end and cleanup_end (if I understood it correctly) ?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, was confused by the naming as well here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair enough! the time-related types in cw are a bit scattered so I felt like having _expiration would add clarity as to what the field is, but I'll change it

} else {
AuctionPhase::Bidding
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this to be very risky and a potential vector for many issues .. maybe I don't have the full context, but I would not rely on block time at all but on block heights only. I have the impression (but correct me if I'm wrong) that there is an implicit / hidden assumption on constant block times and/or (almost) zero down-time. I feel that a design relying on blocks (+ detection for chain downtimes) would be more resilient to the underlying chain infrastructure's failures and also, potentially, more portable.

} else {
Err(StdError::generic_err("order exceeds batch capacity"))
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use unchecked arithmetics.

// otherwise, we still have time to include the order in the active batch that is about to start
// to do that, we first check if the batch has the capacity to fit this order
if current_auction.batch.can_fit_order(&user_intent) {
current_auction.batch.include_order(user_intent)?;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above .. further encapsulate logic: current_auction.can_fit_order& current_auction.include_order would be more readable.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the complexity of the domain, I would avoid structs with all members being public. I'd strive to encapsulate and control access to private members just so as to ensure that domain invariants are enforced no matter what the calling code does.

}

/// moves the user intents from the orderbook queue to the active auction batch
fn process_orderbook(storage: &mut dyn Storage) -> StdResult<()> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also pass the active_auction as param as it's already been loaded from storage before.

{
if let Some(intent) = ORDERBOOK.pop_front(storage)? {
let capacity = active_auction.batch.remaining_capacity();
if intent.amount <= capacity {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the can_fit_order helper func here instead ?

amount,
offer_domain: self.offer_domain.clone(),
ask_domain: self.ask_domain.clone(),
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could take a &mut selfreference, and modify the current intent's amount, and return (self, remainder).

},
};

pub const CONTRACT_NAME: &str = "orbital-auction";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: prefer

const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME");

to not hardcode in two places

Comment on lines +235 to +244
fn slash_solver(storage: &mut dyn Storage, solver: Addr) -> StdResult<()> {
let auction_config = AUCTION_CONFIG.load(storage)?;
let mut posted_bond = POSTED_BONDS.load(storage, solver.to_string())?;
posted_bond.amount = posted_bond
.amount
.checked_sub(auction_config.solver_bond.amount)?;

POSTED_BONDS.save(storage, solver.to_string(), &posted_bond)?;

Ok(())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is done with the slashed amount? If we are not doing anything with it yet or it's not decided maybe add a TODO here

Comment on lines +33 to +38
// bidder actions
/// post a bond to participate in the auction
PostBond {},

/// withdraw the posted bond
WithdrawBond {},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that important: Should we have an increase/decrease bond? If we are happy with Withdrawing and posting again It's fine

Comment on lines +129 to +146
pub fn advance(&self, storage: &mut dyn Storage) -> StdResult<Self> {
let auction_config = AUCTION_CONFIG.load(storage)?;

let next_id = self.id + Uint64::one();

let next_phases = self.phases.shift_phases(&auction_config.auction_phases)?;
let next_batch = Batch {
batch_capacity: auction_config.batch_size,
batch_size: Uint128::zero(),
user_intents: vec![],
current_bid: None,
};

Ok(AuctionRound {
id: next_id,
phases: next_phases,
batch: next_batch,
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we save the AUCTION_CONFIG here instead of after this function?

pub start_expiration: Expiration,
pub auction_expiration: Expiration,
pub filling_expiration: Expiration,
pub cleanup_expiration: Expiration,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, was confused by the naming as well here

Comment on lines +84 to +114
pub struct AuctionPhaseConfig {
// duration of the bidding window in seconds
pub auction_duration: Duration,
// duration of the filling window in seconds
pub filling_window_duration: Duration,
// duration of the cleanup window in seconds
pub cleanup_window_duration: Duration,
}

impl AuctionPhaseConfig {
/// validates that the auction phase configuration is all based on time (seconds).
pub fn validate(&self) -> StdResult<()> {
if let Duration::Height(_) = self.auction_duration {
return Err(StdError::generic_err(
"auction duration must be a time duration",
));
}
if let Duration::Height(_) = self.filling_window_duration {
return Err(StdError::generic_err(
"filling duration must be a time duration",
));
}
if let Duration::Height(_) = self.cleanup_window_duration {
return Err(StdError::generic_err(
"cleanup duration must be a time duration",
));
}

Ok(())
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are using Timestamps only for everything, why not use u64 directly instead of using Duration?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

orbital auction
3 participants