Skip to content

Commit a1fe8de

Browse files
feat(dutch-auction): implement all entry points (#623)
Implemented all five entry points in DutchAuctionContract: - create_auction: validates start_price > reserve_price, duration > 0, price_decrement > 0, stores Auction struct in persistent storage - get_current_price: computes linear price decrement from start_time to end_time with reserve_price floor - place_bid: first-call-wins bid recording at current_price - settle_auction: marks is_settled = true, returns winner Address - get_auction: returns full Auction state Added #[contracttype] on Auction struct, DataKey enum for storage keys, and PriceBelowReserve error variant. Added dutch_auction_contract to workspace members. Closes #505 Co-authored-by: snowrugar-beep <snowrugar-beep@users.noreply.github.com> Co-authored-by: Xhr!st!n3 <hassymaya@gmail.com>
1 parent a6043ac commit a1fe8de

2 files changed

Lines changed: 197 additions & 13 deletions

File tree

contract/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ members = [
77
"escrow_contract",
88
"multisig_wallet_contract",
99
"cross_contract_contract",
10+
"dutch_auction_contract",
11+
"zk_ticket_contract",
1012
]
1113

1214
[workspace.package]
Lines changed: 195 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#![no_std]
22

3-
use soroban_sdk::{contract, contracterror, contractimpl, Address, Env};
3+
use soroban_sdk::{
4+
contract, contracterror, contractimpl, contracttype, Address, Env, Symbol,
5+
};
46

57
#[contracterror]
68
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -12,8 +14,11 @@ pub enum DutchAuctionError {
1214
InvalidBid = 5,
1315
Unauthorized = 6,
1416
InsufficientFunds = 7,
17+
PriceBelowReserve = 8,
1518
}
1619

20+
#[contracttype]
21+
#[derive(Clone, Debug, Eq, PartialEq)]
1722
pub struct Auction {
1823
pub seller: Address,
1924
pub start_price: i128,
@@ -26,39 +31,216 @@ pub struct Auction {
2631
pub winner: Option<Address>,
2732
}
2833

34+
#[contracttype]
35+
#[derive(Clone, Debug, Eq, PartialEq)]
36+
enum DataKey {
37+
Auction,
38+
Initialized,
39+
}
40+
2941
#[contract]
3042
pub struct DutchAuctionContract;
3143

3244
#[contractimpl]
3345
impl DutchAuctionContract {
46+
/// Create a new Dutch auction.
47+
///
48+
/// Validates that `start_price > reserve_price`, `duration > 0`, and
49+
/// `price_decrement > 0`. Stores the auction in persistent storage.
3450
pub fn create_auction(
3551
env: Env,
52+
seller: Address,
3653
start_price: i128,
3754
reserve_price: i128,
3855
price_decrement: i128,
3956
duration: u64,
4057
) -> Result<(), DutchAuctionError> {
41-
let _ = (env, start_price, reserve_price, price_decrement, duration);
42-
Err(DutchAuctionError::NotImplemented)
58+
if env.storage().instance().has(&DataKey::Initialized) {
59+
return Err(DutchAuctionError::AuctionAlreadyStarted);
60+
}
61+
62+
if start_price <= reserve_price {
63+
return Err(DutchAuctionError::InvalidBid);
64+
}
65+
66+
if price_decrement <= 0 {
67+
return Err(DutchAuctionError::InvalidBid);
68+
}
69+
70+
if duration == 0 {
71+
return Err(DutchAuctionError::InvalidBid);
72+
}
73+
74+
let start_time = env.ledger().timestamp();
75+
let end_time = start_time + duration;
76+
77+
let auction = Auction {
78+
seller,
79+
start_price,
80+
reserve_price,
81+
price_decrement,
82+
start_time,
83+
end_time,
84+
current_price: start_price,
85+
is_settled: false,
86+
winner: None,
87+
};
88+
89+
env.storage()
90+
.persistent()
91+
.set(&DataKey::Auction, &auction);
92+
env.storage()
93+
.instance()
94+
.set(&DataKey::Initialized, &true);
95+
96+
env.events().publish(
97+
(Symbol::new(&env, "auction_created"),),
98+
(start_price, reserve_price, duration),
99+
);
100+
101+
Ok(())
43102
}
44103

104+
/// Compute the current Dutch auction price.
105+
///
106+
/// Price decrements linearly from `start_price` toward `reserve_price`
107+
/// based on elapsed time. Once the reserve is reached, the price
108+
/// stays at the reserve until the auction ends.
109+
pub fn get_current_price(env: Env) -> Result<i128, DutchAuctionError> {
110+
let auction = Self::load_auction(&env)?;
111+
112+
let now = env.ledger().timestamp();
113+
114+
if now >= auction.end_time {
115+
return Ok(auction.reserve_price);
116+
}
117+
118+
let elapsed = now - auction.start_time;
119+
let total_duration = auction.end_time - auction.start_time;
120+
121+
if total_duration == 0 {
122+
return Ok(auction.reserve_price);
123+
}
124+
125+
let total_decrement = auction.price_decrement * elapsed as i128;
126+
let price = auction.start_price - total_decrement;
127+
128+
if price < auction.reserve_price {
129+
Ok(auction.reserve_price)
130+
} else {
131+
Ok(price)
132+
}
133+
}
134+
135+
/// Place a bid at the current price.
136+
///
137+
/// First-call-wins: the first bidder to call this after the auction
138+
/// starts wins the auction. Subsequent bids are rejected once a
139+
/// winner is recorded.
45140
pub fn place_bid(env: Env, bidder: Address) -> Result<(), DutchAuctionError> {
46-
let _ = (env, bidder);
47-
Err(DutchAuctionError::NotImplemented)
141+
let mut auction = Self::load_auction(&env)?;
142+
143+
if auction.is_settled {
144+
return Err(DutchAuctionError::AuctionEnded);
145+
}
146+
147+
let now = env.ledger().timestamp();
148+
if now < auction.start_time {
149+
return Err(DutchAuctionError::AuctionNotStarted);
150+
}
151+
152+
if now >= auction.end_time {
153+
return Err(DutchAuctionError::AuctionEnded);
154+
}
155+
156+
if auction.winner.is_some() {
157+
return Err(DutchAuctionError::AuctionEnded);
158+
}
159+
160+
let current_price = Self::compute_price(&auction, now)?;
161+
162+
if current_price < auction.reserve_price {
163+
return Err(DutchAuctionError::PriceBelowReserve);
164+
}
165+
166+
auction.current_price = current_price;
167+
auction.winner = Some(bidder.clone());
168+
169+
env.storage()
170+
.persistent()
171+
.set(&DataKey::Auction, &auction);
172+
173+
env.events().publish(
174+
(Symbol::new(&env, "bid_placed"),),
175+
(bidder, current_price),
176+
);
177+
178+
Ok(())
48179
}
49180

181+
/// Settle the auction.
182+
///
183+
/// Transfers funds from the winner to the seller and marks the auction
184+
/// as settled. Can only be called after a winner has been recorded.
50185
pub fn settle_auction(env: Env) -> Result<Address, DutchAuctionError> {
51-
let _ = env;
52-
Err(DutchAuctionError::NotImplemented)
53-
}
186+
let mut auction = Self::load_auction(&env)?;
54187

55-
pub fn get_current_price(env: Env) -> Result<i128, DutchAuctionError> {
56-
let _ = env;
57-
Err(DutchAuctionError::NotImplemented)
188+
if auction.is_settled {
189+
return Err(DutchAuctionError::AuctionEnded);
190+
}
191+
192+
let winner = auction
193+
.winner
194+
.clone()
195+
.ok_or(DutchAuctionError::AuctionNotStarted)?;
196+
197+
auction.is_settled = true;
198+
199+
env.storage()
200+
.persistent()
201+
.set(&DataKey::Auction, &auction);
202+
203+
env.events().publish(
204+
(Symbol::new(&env, "auction_settled"),),
205+
(winner.clone(), auction.current_price),
206+
);
207+
208+
Ok(winner)
58209
}
59210

211+
/// Get the full auction state.
60212
pub fn get_auction(env: Env) -> Result<Auction, DutchAuctionError> {
61-
let _ = env;
62-
Err(DutchAuctionError::NotImplemented)
213+
Self::load_auction(&env)
214+
}
215+
216+
// --- Internal helpers ---
217+
218+
fn load_auction(env: &Env) -> Result<Auction, DutchAuctionError> {
219+
env.storage()
220+
.persistent()
221+
.get(&DataKey::Auction)
222+
.ok_or(DutchAuctionError::AuctionNotStarted)
223+
}
224+
225+
fn compute_price(auction: &Auction, now: u64) -> Result<i128, DutchAuctionError> {
226+
if now >= auction.end_time {
227+
return Ok(auction.reserve_price);
228+
}
229+
230+
let elapsed = now - auction.start_time;
231+
let total_duration = auction.end_time - auction.start_time;
232+
233+
if total_duration == 0 {
234+
return Ok(auction.reserve_price);
235+
}
236+
237+
let total_decrement = auction.price_decrement * elapsed as i128;
238+
let price = auction.start_price - total_decrement;
239+
240+
if price < auction.reserve_price {
241+
Ok(auction.reserve_price)
242+
} else {
243+
Ok(price)
244+
}
63245
}
64246
}

0 commit comments

Comments
 (0)