11mod lib ;
22
3- use dep:: aztec::macros::aztec ;
3+ use aztec::macros::aztec ;
44
55/// Binary prediction market with full identity privacy using CSMM pricing.
66/// All balances are private. Public function only sees trade amounts, not who traded.
77#[aztec]
88pub contract PredictionMarket {
99 use crate::lib:: {calculate_shares_out , calculate_new_price , calculate_price_impact };
10- use dep:: aztec:: {
11- macros ::{functions ::{external , initializer , internal }, storage:: storage },
10+ use aztec:: {
11+ macros ::{functions ::{external , initializer , only_self }, storage:: storage },
1212 messages::message_delivery::MessageDelivery ,
13- note ::{
14- constants::MAX_NOTES_PER_PAGE ,
15- note_getter_options::NoteGetterOptions ,
16- note_viewer_options::NoteViewerOptions ,
17- },
18- protocol_types ::{address::AztecAddress , storage::map:: derive_storage_slot_in_map },
19- state_vars ::{PublicMutable , Map , PrivateSet },
13+ protocol_types::address::AztecAddress ,
14+ state_vars ::{PublicMutable , Owned , state_variable::StateVariable },
2015 };
16+ use dep::balance_set::balance_set::BalanceSet ;
2117 use dep::uint_note::uint_note:: {UintNote , PartialUintNote };
2218
2319 #[storage]
2420 struct Storage <Context > {
25- collateral_balances : Map < AztecAddress , PrivateSet < UintNote , Context >, Context >,
21+ collateral_balances : Owned < BalanceSet < Context >, Context >,
2622 yes_supply : PublicMutable <u128 , Context >,
2723 no_supply : PublicMutable <u128 , Context >,
2824 total_liquidity : PublicMutable <u128 , Context >,
2925 admin : PublicMutable <AztecAddress , Context >,
30- yes_balances : Map < AztecAddress , PrivateSet < UintNote , Context >, Context >,
31- no_balances : Map < AztecAddress , PrivateSet < UintNote , Context >, Context >,
26+ yes_balances : Owned < BalanceSet < Context >, Context >,
27+ no_balances : Owned < BalanceSet < Context >, Context >,
3228 }
3329
3430 pub global PRICE_PRECISION : u128 = 1 _000 _000 ;
@@ -40,45 +36,29 @@ pub contract PredictionMarket {
4036 assert (initial_liquidity >= MINIMUM_LIQUIDITY , "INSUFFICIENT_INITIAL_LIQUIDITY" );
4137 assert (!admin .is_zero (), "INVALID_ADMIN" );
4238
43- storage .admin .write (admin );
39+ self . storage .admin .write (admin );
4440 let half = initial_liquidity / 2 ;
45- storage .yes_supply .write (half );
46- storage .no_supply .write (half );
47- storage .total_liquidity .write (initial_liquidity );
41+ self . storage .yes_supply .write (half );
42+ self . storage .no_supply .write (half );
43+ self . storage .total_liquidity .write (initial_liquidity );
4844 }
4945
5046 /// Deposit collateral privately. Creates a private note for the sender.
5147 #[external("private")]
5248 fn deposit (amount : u128 ) {
5349 assert (amount > 0 , "ZERO_AMOUNT" );
54- let sender = context .msg_sender ().unwrap ();
55- let note = UintNote :: new ( amount , sender );
56- storage . collateral_balances . at ( sender ). insert ( note ). emit ( sender , MessageDelivery .UNCONSTRAINED_ONCHAIN );
50+ let sender = self . context .msg_sender ().unwrap ();
51+ self . storage . collateral_balances . at ( sender ). add ( amount )
52+ . deliver ( MessageDelivery .UNCONSTRAINED_ONCHAIN );
5753 }
5854
5955 /// Withdraw collateral privately. Consumes notes and returns change.
6056 #[external("private")]
6157 fn withdraw (amount : u128 ) {
6258 assert (amount > 0 , "ZERO_AMOUNT" );
63- let sender = context .msg_sender ().unwrap ();
64-
65- let options = NoteGetterOptions ::new ();
66- let notes : BoundedVec <UintNote , 16 > = storage .collateral_balances .at (sender ).pop_notes (options );
67-
68- let mut total : u128 = 0 ;
69- for i in 0 ..16 {
70- if i < notes .len () {
71- total += notes .get_unchecked (i ).get_value ();
72- }
73- }
74-
75- assert (total >= amount , "INSUFFICIENT_COLLATERAL_BALANCE" );
76-
77- let change = total - amount ;
78- if change > 0 {
79- let change_note = UintNote ::new (change , sender );
80- storage .collateral_balances .at (sender ).insert (change_note ).emit (sender , MessageDelivery .UNCONSTRAINED_ONCHAIN );
81- }
59+ let sender = self .context .msg_sender ().unwrap ();
60+ self .storage .collateral_balances .at (sender ).sub (amount )
61+ .deliver (MessageDelivery .UNCONSTRAINED_ONCHAIN );
8262 }
8363
8464 /// Buy YES or NO shares with full identity privacy.
@@ -87,51 +67,33 @@ pub contract PredictionMarket {
8767 fn buy_outcome (is_yes : bool , collateral_amount : u128 , min_shares_out : u128 ) {
8868 assert (collateral_amount > 0 , "INSUFFICIENT_COLLATERAL" );
8969
90- let sender = context .msg_sender ().unwrap ();
91- let contract_address = context .this_address ();
92-
93- // Consume collateral notes
94- let options = NoteGetterOptions ::new ();
95- let notes : BoundedVec <UintNote , 16 > = storage .collateral_balances .at (sender ).pop_notes (options );
96-
97- let mut total : u128 = 0 ;
98- for i in 0 ..16 {
99- if i < notes .len () {
100- total += notes .get_unchecked (i ).get_value ();
101- }
102- }
103-
104- assert (total >= collateral_amount , "INSUFFICIENT_COLLATERAL_BALANCE" );
70+ let sender = self .context .msg_sender ().unwrap ();
71+ let contract_address = self .context .this_address ();
10572
106- // Return change
107- let change = total - collateral_amount ;
108- if change > 0 {
109- let change_note = UintNote ::new (change , sender );
110- storage .collateral_balances .at (sender ).insert (change_note ).emit (sender , MessageDelivery .UNCONSTRAINED_ONCHAIN );
111- }
73+ // Consume collateral - sub handles note consumption and change
74+ self .storage .collateral_balances .at (sender ).sub (collateral_amount )
75+ .deliver (MessageDelivery .UNCONSTRAINED_ONCHAIN );
11276
11377 // Create partial note for shares
114- // Storage slots are required for deriving partial note locations.
115- // This is because the partial note needs to be manually placed at that slot because we cannot use PrivateSet::insert().
116- // We cannot use PrivateSet::insert() because the note does not exist yet, as it is not fully created in private.
117-
118- let base_slot = if is_yes { PredictionMarket :: storage_layout (). yes_balances . slot } else { PredictionMarket :: storage_layout (). no_balances .slot };
119- let storage_slot : Field = derive_storage_slot_in_map ( base_slot , sender ) ;
120- let partial_note = UintNote ::partial (sender , storage_slot , & mut context , sender , contract_address );
78+ // Use get_storage_slot() from the Owned wrapper - it handles owner derivation internally
79+ let storage_slot = if is_yes {
80+ self . storage . yes_balances . get_storage_slot ()
81+ } else {
82+ self . storage . no_balances .get_storage_slot ()
83+ } ;
84+ let partial_note = UintNote ::partial (sender , storage_slot , self . context , sender , contract_address );
12185
12286 // Enqueue public call - sender address is NOT passed, preserving identity privacy
123- PredictionMarket ::at (contract_address )
124- ._process_buy (is_yes , collateral_amount , min_shares_out , partial_note )
125- .enqueue (&mut context );
87+ self .enqueue_self ._process_buy (is_yes , collateral_amount , min_shares_out , partial_note );
12688 }
12789
12890 /// Process buy in public context. Does not know who is buying.
129- #[internal ]
91+ #[only_self ]
13092 #[external("public")]
13193 fn _process_buy (is_yes : bool , collateral_amount : u128 , min_shares_out : u128 , partial_note : PartialUintNote ) {
132- let yes_supply = storage .yes_supply .read ();
133- let no_supply = storage .no_supply .read ();
134- let liquidity = storage .total_liquidity .read ();
94+ let yes_supply = self . storage .yes_supply .read ();
95+ let no_supply = self . storage .no_supply .read ();
96+ let liquidity = self . storage .total_liquidity .read ();
13597
13698 let (shares_out , new_supply ) = calculate_shares_out (
13799 collateral_amount ,
@@ -142,35 +104,35 @@ pub contract PredictionMarket {
142104 assert (shares_out >= min_shares_out , "SLIPPAGE_EXCEEDED" );
143105
144106 if is_yes {
145- storage .yes_supply .write (new_supply );
107+ self . storage .yes_supply .write (new_supply );
146108 } else {
147- storage .no_supply .write (new_supply );
109+ self . storage .no_supply .write (new_supply );
148110 }
149- storage .total_liquidity .write (liquidity + collateral_amount );
111+ self . storage .total_liquidity .write (liquidity + collateral_amount );
150112
151- partial_note .complete (& mut context , context .this_address (), shares_out );
113+ partial_note .complete (self . context , self . context .this_address (), shares_out );
152114 }
153115
154116 /// Get current price for YES or NO (returns value with PRICE_PRECISION decimals).
155117 #[external("utility")]
156- unconstrained fn get_price (is_yes : bool ) -> u128 {
157- let yes_supply = storage .yes_supply .read ();
158- let no_supply = storage .no_supply .read ();
118+ unconstrained fn get_price (is_yes : bool ) -> pub u128 {
119+ let yes_supply = self . storage .yes_supply .read ();
120+ let no_supply = self . storage .no_supply .read ();
159121 calculate_new_price (if is_yes { yes_supply } else { no_supply }, yes_supply + no_supply )
160122 }
161123
162124 /// Get market state: (yes_supply, no_supply, total_liquidity).
163125 #[external("utility")]
164- unconstrained fn get_market_state () -> (u128 , u128 , u128 ) {
165- (storage .yes_supply .read (), storage .no_supply .read (), storage .total_liquidity .read ())
126+ unconstrained fn get_market_state () -> pub (u128 , u128 , u128 ) {
127+ (self . storage .yes_supply .read (), self . storage .no_supply .read (), self . storage .total_liquidity .read ())
166128 }
167129
168130 /// Quote shares out for a given collateral amount.
169131 #[external("utility")]
170- unconstrained fn quote_buy (is_yes : bool , collateral_amount : u128 ) -> u128 {
171- let yes_supply = storage .yes_supply .read ();
172- let no_supply = storage .no_supply .read ();
173- let liquidity = storage .total_liquidity .read ();
132+ unconstrained fn quote_buy (is_yes : bool , collateral_amount : u128 ) -> pub u128 {
133+ let yes_supply = self . storage .yes_supply .read ();
134+ let no_supply = self . storage .no_supply .read ();
135+ let liquidity = self . storage .total_liquidity .read ();
174136 let (shares_out , _ ) = calculate_shares_out (
175137 collateral_amount ,
176138 if is_yes { yes_supply } else { no_supply },
@@ -181,54 +143,38 @@ pub contract PredictionMarket {
181143
182144 /// Quote price impact for a purchase.
183145 /// Returns: (price_before, price_after, impact_percentage)
184- /// All values use PRICE_PRECISION (1_000_000) as the decimal base.
185146 #[external("utility")]
186- unconstrained fn quote_price_impact (is_yes : bool , collateral_amount : u128 ) -> (u128 , u128 , u128 ) {
187- let yes_supply = storage .yes_supply .read ();
188- let no_supply = storage .no_supply .read ();
189- let liquidity = storage .total_liquidity .read ();
147+ unconstrained fn quote_price_impact (is_yes : bool , collateral_amount : u128 ) -> pub (u128 , u128 , u128 ) {
148+ let yes_supply = self . storage .yes_supply .read ();
149+ let no_supply = self . storage .no_supply .read ();
150+ let liquidity = self . storage .total_liquidity .read ();
190151 calculate_price_impact (
191152 collateral_amount ,
192153 if is_yes { yes_supply } else { no_supply },
193154 liquidity ,
194155 )
195156 }
196157
197- /// Get user's private collateral balance.
158+ /// Get user's private collateral balance (unconstrained view) .
198159 #[external("utility")]
199- unconstrained fn get_collateral_balance (user : AztecAddress ) -> u128 {
200- let notes : BoundedVec <UintNote , MAX_NOTES_PER_PAGE > = storage .collateral_balances .at (user ).view_notes (NoteViewerOptions ::new ());
201- let mut sum : u128 = 0 ;
202- for i in 0 ..notes .len () {
203- sum += notes .get_unchecked (i ).get_value ();
204- }
205- sum
160+ unconstrained fn get_collateral_balance (user : AztecAddress ) -> pub u128 {
161+ self .storage .collateral_balances .at (user ).balance_of ()
206162 }
207163
208- /// Get user's private YES token balance.
164+ /// Get user's private YES token balance (unconstrained view) .
209165 #[external("utility")]
210- unconstrained fn get_yes_balance (user : AztecAddress ) -> u128 {
211- let notes : BoundedVec <UintNote , MAX_NOTES_PER_PAGE > = storage .yes_balances .at (user ).view_notes (NoteViewerOptions ::new ());
212- let mut sum : u128 = 0 ;
213- for i in 0 ..notes .len () {
214- sum += notes .get_unchecked (i ).get_value ();
215- }
216- sum
166+ unconstrained fn get_yes_balance (user : AztecAddress ) -> pub u128 {
167+ self .storage .yes_balances .at (user ).balance_of ()
217168 }
218169
219- /// Get user's private NO token balance.
170+ /// Get user's private NO token balance (unconstrained view) .
220171 #[external("utility")]
221- unconstrained fn get_no_balance (user : AztecAddress ) -> u128 {
222- let notes : BoundedVec <UintNote , MAX_NOTES_PER_PAGE > = storage .no_balances .at (user ).view_notes (NoteViewerOptions ::new ());
223- let mut sum : u128 = 0 ;
224- for i in 0 ..notes .len () {
225- sum += notes .get_unchecked (i ).get_value ();
226- }
227- sum
172+ unconstrained fn get_no_balance (user : AztecAddress ) -> pub u128 {
173+ self .storage .no_balances .at (user ).balance_of ()
228174 }
229175
230176 #[external("utility")]
231- unconstrained fn get_admin () -> AztecAddress {
232- storage .admin .read ()
177+ unconstrained fn get_admin () -> pub AztecAddress {
178+ self . storage .admin .read ()
233179 }
234180}
0 commit comments