Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 87 additions & 158 deletions gateway-contract/contracts/auction_contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,180 +1,109 @@
#![no_std]
use soroban_sdk::{contract, contractimpl, vec, Address, BytesN, Env, IntoVal, Symbol};

pub mod errors;
pub mod events;
pub mod storage;
pub mod types;

// Ensure event symbols are linked from the main contract entrypoint module.
use crate::events::{AUCTION_CLOSED, AUCTION_CREATED, BID_PLACED, BID_REFUNDED, USERNAME_CLAIMED};

#[allow(dead_code)]
fn _touch_event_symbols() {
let _ = (
AUCTION_CREATED,
BID_PLACED,
AUCTION_CLOSED,
USERNAME_CLAIMED,
BID_REFUNDED,
);
}

#[cfg(test)]
mod test;
mod storage;
mod types;

pub use storage::{
add_bidder, get_all_bidders, get_auction, get_bid, has_auction, set_auction, set_bid, DataKey,
};
pub use types::{AuctionState, Bid};

use soroban_sdk::{contract, contractimpl, Address, BytesN, Env, Vec};

#[contract]
pub struct AuctionContract;

#[contractimpl]
impl AuctionContract {
pub fn close_auction(
// -----------------------------------------------------------------------
// Auction management
// -----------------------------------------------------------------------

/// Create a new auction identified by `hash`.
///
/// The `hash` must not already be in use — callers should verify with
/// `has_auction` before calling this. The creator must authorise the
/// call.
///
/// # Errors
/// Panics with `"auction already exists"` when `hash` is already in use.
pub fn create_auction(
env: Env,
username_hash: BytesN<32>,
) -> Result<(), crate::errors::AuctionError> {
let status = storage::get_status(&env);

// Reject if status is not Open
if status != types::AuctionStatus::Open {
return Err(crate::errors::AuctionError::AuctionNotOpen);
}

// Get current ledger timestamp and end time
let current_time = env.ledger().timestamp();
let end_time = storage::get_end_time(&env);

// Reject if timestamp < end_time
if current_time < end_time {
return Err(crate::errors::AuctionError::AuctionNotClosed);
}

// Set status to Closed
storage::set_status(&env, types::AuctionStatus::Closed);

// Get winner and winning bid
let winner = storage::get_highest_bidder(&env);
let winning_bid = storage::get_highest_bid(&env);

// Emit AUCTION_CLOSED event with winner and winning bid
events::emit_auction_closed(&env, &username_hash, winner.clone(), winning_bid);

Ok(())
creator: Address,
hash: BytesN<32>,
start_time: u64,
end_time: u64,
reserve_price: i128,
) {
creator.require_auth();
assert!(!has_auction(&env, &hash), "auction already exists");

let state = AuctionState {
creator,
start_time,
end_time,
reserve_price,
highest_bid: 0,
highest_bidder: None,
is_settled: false,
};
set_auction(&env, &hash, &state);
}

pub fn claim_username(
env: Env,
username_hash: BytesN<32>,
claimer: Address,
) -> Result<(), crate::errors::AuctionError> {
claimer.require_auth();

let status = storage::get_status(&env);

if status == types::AuctionStatus::Claimed {
return Err(crate::errors::AuctionError::AlreadyClaimed);
}

if status != types::AuctionStatus::Closed {
return Err(crate::errors::AuctionError::NotClosed);
}

let highest_bidder = storage::get_highest_bidder(&env);
if !highest_bidder.map(|h| h == claimer).unwrap_or(false) {
return Err(crate::errors::AuctionError::NotWinner);
}

// Set status to Claimed
storage::set_status(&env, types::AuctionStatus::Claimed);

// Call factory_contract.deploy_username(username_hash, claimer)
let factory = storage::get_factory_contract(&env);
if factory.is_none() {
return Err(crate::errors::AuctionError::NoFactoryContract);
}

let factory_addr = factory.ok_or(crate::errors::AuctionError::NoFactoryContract)?;
env.invoke_contract::<()>(
&factory_addr,
&Symbol::new(&env, "deploy_username"),
vec![&env, username_hash.into_val(&env), claimer.into_val(&env)],
);

// Emit USERNAME_CLAIMED event
events::emit_username_claimed(&env, &username_hash, &claimer);

Ok(())
/// View: returns the full [`AuctionState`] for `hash`.
///
/// # Errors
/// Panics with `"auction not found"` when `hash` has no associated state.
pub fn get_auction(env: Env, hash: BytesN<32>) -> AuctionState {
get_auction(&env, &hash).expect("auction not found")
}
}

#[contractimpl]
impl AuctionContract {
pub fn create_auction(
env: Env,
id: u32,
seller: Address,
asset: Address,
min_bid: i128,
end_time: u64,
) {
seller.require_auth();
if storage::auction_exists(&env, id) {
soroban_sdk::panic_with_error!(&env, errors::AuctionError::AuctionNotOpen);
}
storage::auction_set_seller(&env, id, &seller);
storage::auction_set_asset(&env, id, &asset);
storage::auction_set_min_bid(&env, id, min_bid);
storage::auction_set_end_time(&env, id, end_time);
storage::auction_set_status(&env, id, types::AuctionStatus::Open);
/// View: returns `true` if an auction exists for `hash`.
pub fn has_auction(env: Env, hash: BytesN<32>) -> bool {
has_auction(&env, &hash)
}

pub fn place_bid(env: Env, id: u32, bidder: Address, amount: i128) {
// -----------------------------------------------------------------------
// Bidding
// -----------------------------------------------------------------------

/// Place or update a bid from `bidder` on auction `hash`.
///
/// * The auction must exist.
/// * `amount` must exceed the current `highest_bid`.
/// * `bidder` must authorise the call.
///
/// # Errors
/// Panics on constraint violations (auction missing, bid too low).
pub fn place_bid(env: Env, hash: BytesN<32>, bidder: Address, amount: i128) {
bidder.require_auth();
let end_time = storage::auction_get_end_time(&env, id);
if env.ledger().timestamp() >= end_time {
soroban_sdk::panic_with_error!(&env, errors::AuctionError::AuctionNotOpen);
}
let min_bid = storage::auction_get_min_bid(&env, id);
let highest_bid = storage::auction_get_highest_bid(&env, id);
if amount < min_bid || amount <= highest_bid {
soroban_sdk::panic_with_error!(&env, errors::AuctionError::BidTooLow);
}
let asset = storage::auction_get_asset(&env, id);
let token = soroban_sdk::token::Client::new(&env, &asset);
token.transfer(&bidder, env.current_contract_address(), &amount);
if let Some(prev_bidder) = storage::auction_get_highest_bidder(&env, id) {
token.transfer(&env.current_contract_address(), &prev_bidder, &highest_bid);
}
storage::auction_set_highest_bidder(&env, id, &bidder);
storage::auction_set_highest_bid(&env, id, amount);

let mut state = get_auction(&env, &hash).expect("auction not found");
assert!(amount > state.highest_bid, "bid must exceed highest bid");

let bid = Bid {
bidder: bidder.clone(),
amount,
timestamp: env.ledger().timestamp(),
};

// Update the bidder list before writing the bid record so that the
// AllBidders key is always at least as fresh as any Bid key.
add_bidder(&env, &hash, bidder.clone());
set_bid(&env, &hash, &bidder, &bid);

state.highest_bid = amount;
state.highest_bidder = Some(bidder);
set_auction(&env, &hash, &state);
}

pub fn close_auction_by_id(env: Env, id: u32) {
let end_time = storage::auction_get_end_time(&env, id);
if env.ledger().timestamp() < end_time {
soroban_sdk::panic_with_error!(&env, errors::AuctionError::AuctionNotClosed);
}
storage::auction_set_status(&env, id, types::AuctionStatus::Closed);
/// View: returns the [`Bid`] placed by `bidder` on `hash`, if any.
pub fn get_bid(env: Env, hash: BytesN<32>, bidder: Address) -> Option<Bid> {
get_bid(&env, &hash, &bidder)
}

pub fn claim(env: Env, id: u32, claimant: Address) {
claimant.require_auth();
let status = storage::auction_get_status(&env, id);
if status != types::AuctionStatus::Closed {
soroban_sdk::panic_with_error!(&env, errors::AuctionError::NotClosed);
}
if storage::auction_is_claimed(&env, id) {
soroban_sdk::panic_with_error!(&env, errors::AuctionError::AlreadyClaimed);
}
let winner = storage::auction_get_highest_bidder(&env, id);
if winner.as_ref().map(|w| w == &claimant).unwrap_or(false) {
let asset = storage::auction_get_asset(&env, id);
let token = soroban_sdk::token::Client::new(&env, &asset);
let winning_bid = storage::auction_get_highest_bid(&env, id);
let seller = storage::auction_get_seller(&env, id);
token.transfer(&env.current_contract_address(), &seller, &winning_bid);
storage::auction_set_claimed(&env, id);
} else {
soroban_sdk::panic_with_error!(&env, errors::AuctionError::NotWinner);
}
/// View: returns all addresses that have bid on `hash`.
pub fn get_all_bidders(env: Env, hash: BytesN<32>) -> Vec<Address> {
get_all_bidders(&env, &hash)
}
}
Loading
Loading