pcli/command/tx/auction/dutch/
gda.rs1use clap::ArgEnum;
2use penumbra_sdk_asset::Value;
3use penumbra_sdk_auction::auction::dutch::DutchAuctionDescription;
4use rand::Rng;
5use rand::RngCore;
6use rand_core::OsRng;
7use serde::Serialize;
8
9#[derive(ArgEnum, Clone, Debug, Serialize)]
10pub enum GdaRecipe {
11 #[clap(name = "10m")]
12 TenMinutes,
13 #[clap(name = "30m")]
14 ThirtyMinutes,
15 #[clap(name = "1h")]
16 OneHour,
17 #[clap(name = "2h")]
18 TwoHours,
19 #[clap(name = "6h")]
20 SixHours,
21 #[clap(name = "12h")]
22 TwelveHours,
23 #[clap(name = "1d")]
24 OneDay,
25 #[clap(name = "2d")]
26 TwoDays,
27}
28
29impl GdaRecipe {
30 pub fn as_blocks(&self) -> u64 {
31 match self {
32 GdaRecipe::TenMinutes => 10 * 12,
33 GdaRecipe::ThirtyMinutes => 30 * 12,
34 GdaRecipe::OneHour => 60 * 12,
35 GdaRecipe::TwoHours => 2 * 60 * 12,
36 GdaRecipe::SixHours => 6 * 60 * 12,
37 GdaRecipe::TwelveHours => 12 * 60 * 12,
38 GdaRecipe::OneDay => 24 * 60 * 12,
39 GdaRecipe::TwoDays => 2 * 24 * 60 * 12,
40 }
41 }
42
43 pub fn poisson_intensity(&self) -> f64 {
44 match &self {
45 GdaRecipe::TenMinutes => 0.0645833333333,
46 GdaRecipe::ThirtyMinutes => 0.05058333333,
47 GdaRecipe::OneHour => 0.02525,
48 GdaRecipe::TwoHours => 0.02266666666,
49 GdaRecipe::SixHours => 0.01075,
50 GdaRecipe::TwelveHours => 0.0053333333,
51 GdaRecipe::OneDay => 0.035,
52 GdaRecipe::TwoDays => 0.00175,
53 }
54 }
55
56 pub fn num_auctions(&self) -> u64 {
57 match self {
58 GdaRecipe::TenMinutes => 4,
59 GdaRecipe::ThirtyMinutes => 12,
60 GdaRecipe::OneHour => 12,
61 GdaRecipe::TwoHours => 24,
62 GdaRecipe::SixHours => 36,
63 GdaRecipe::TwelveHours => 36,
64 GdaRecipe::OneDay => 48,
65 GdaRecipe::TwoDays => 48,
66 }
67 }
68
69 pub fn sub_auction_length(&self) -> u64 {
70 match &self {
71 GdaRecipe::TenMinutes => 60,
72 GdaRecipe::ThirtyMinutes => 60,
73 GdaRecipe::OneHour => 120,
74 GdaRecipe::TwoHours => 120,
75 GdaRecipe::SixHours => 240,
76 GdaRecipe::TwelveHours => 480,
77 GdaRecipe::OneDay => 720,
78 GdaRecipe::TwoDays => 1440,
79 }
80 }
81
82 pub fn step_count(&self) -> u64 {
83 60
84 }
85}
86
87#[derive(Debug, Serialize)]
88pub struct GradualAuction {
89 pub input: Value,
90 pub max_output: Value,
91 pub min_output: Value,
92 pub recipe: GdaRecipe,
93 pub start_height: u64,
94}
95
96impl GradualAuction {
97 pub fn new(
98 input: Value,
99 max_output: Value,
100 min_output: Value,
101 recipe: GdaRecipe,
102 start_height: u64,
103 ) -> Self {
104 GradualAuction {
105 input,
106 max_output,
107 min_output,
108 recipe,
109 start_height,
110 }
111 }
112
113 pub fn generate_start_heights(&self) -> Vec<u64> {
114 let lambda = self.recipe.poisson_intensity();
115 let num_auctions = self.recipe.num_auctions();
116 let start_height = self.start_height;
117 let sub_auction_length = self.recipe.sub_auction_length();
118 tracing::debug!(
119 lambda,
120 num_auctions,
121 start_height,
122 sub_auction_length,
123 num_blocks = self.recipe.as_blocks(),
124 "generating auction starts"
125 );
126
127 let mut rng = rand::thread_rng();
128 let mut current_height = start_height as f64;
129
130 let mut auction_starts = Vec::with_capacity(num_auctions as usize);
131 for _ in 0..num_auctions {
132 let ff_clock = (rng.gen::<f64>() / lambda).ln().abs();
136 current_height += ff_clock;
137 let height = current_height.ceil() as u64;
138 tracing::debug!(height, arrival_time = ff_clock, "selected auction start");
139 auction_starts.push(height)
140 }
141
142 auction_starts
143 }
144
145 pub fn generate_auctions(&self) -> Vec<DutchAuctionDescription> {
146 let start_heights = self.generate_start_heights();
147 let sub_auction_length = self.recipe.sub_auction_length();
148 let step_count = self.recipe.step_count();
149 let num_auctions = self.recipe.num_auctions();
150 let mut auctions = Vec::with_capacity(num_auctions as usize);
151 for start_height in start_heights {
152 let amount_chunk = self.input.amount.value() / num_auctions as u128;
153 let input_chunk = Value {
154 asset_id: self.input.asset_id,
155 amount: amount_chunk.into(),
156 };
157
158 let scaled_min_output = self.min_output.amount.value() / num_auctions as u128;
159 let scaled_max_output = self.max_output.amount.value() / num_auctions as u128;
160
161 let mut nonce = [0u8; 32];
162 OsRng.fill_bytes(&mut nonce);
163
164 let end_height = start_height + sub_auction_length;
165 tracing::debug!(
166 start_height,
167 end_height,
168 sub_auction_length,
169 step_count,
170 ?input_chunk,
171 "generating auction"
172 );
173
174 let auction = DutchAuctionDescription {
175 input: input_chunk,
176 output_id: self.max_output.asset_id,
177 max_output: scaled_max_output.into(),
178 min_output: scaled_min_output.into(),
179 start_height,
180 end_height,
181 step_count,
182 nonce,
183 };
184 auctions.push(auction);
185 }
186 auctions
187 }
188}