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 ) ]
1722pub 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]
3042pub struct DutchAuctionContract ;
3143
3244#[ contractimpl]
3345impl 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