1use chrono::prelude::*;
7use serde_derive::{Deserialize, Serialize};
8use solana_sdk::hash::Hash;
9use solana_sdk::pubkey::Pubkey;
10
11#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
13pub enum Witness {
14 Timestamp(DateTime<Utc>),
16
17 Signature,
19
20 AccountData(Hash, Pubkey),
22}
23
24#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
26pub struct Payment {
27 pub lamports: u64,
29
30 pub to: Pubkey,
32}
33
34#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
40pub struct AccountConstraints {
41 pub key: Pubkey,
43
44 pub program_id: Pubkey,
46
47 pub data_hash: Hash,
49}
50
51#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
53pub enum Condition {
54 Timestamp(DateTime<Utc>, Pubkey),
56
57 Signature(Pubkey),
59
60 AccountData(AccountConstraints),
62}
63
64impl Condition {
65 pub fn is_satisfied(&self, witness: &Witness, from: &Pubkey) -> bool {
67 match (self, witness) {
68 (Condition::Signature(pubkey), Witness::Signature) => pubkey == from,
69 (Condition::Timestamp(dt, pubkey), Witness::Timestamp(last_time)) => {
70 pubkey == from && dt <= last_time
71 }
72 (
73 Condition::AccountData(constraints),
74 Witness::AccountData(actual_hash, program_id),
75 ) => {
76 constraints.program_id == *program_id
77 && constraints.key == *from
78 && constraints.data_hash == *actual_hash
79 }
80 _ => false,
81 }
82 }
83}
84
85#[repr(C)]
87#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
88pub enum BudgetExpr {
89 Pay(Payment),
91
92 After(Condition, Box<BudgetExpr>),
94
95 Or((Condition, Box<BudgetExpr>), (Condition, Box<BudgetExpr>)),
98
99 And(Condition, Condition, Box<BudgetExpr>),
101}
102
103impl BudgetExpr {
104 pub fn new_payment(lamports: u64, to: &Pubkey) -> Self {
106 BudgetExpr::Pay(Payment { lamports, to: *to })
107 }
108
109 pub fn new_authorized_payment(from: &Pubkey, lamports: u64, to: &Pubkey) -> Self {
111 BudgetExpr::After(
112 Condition::Signature(*from),
113 Box::new(Self::new_payment(lamports, to)),
114 )
115 }
116
117 pub fn new_payment_when_account_data(
119 account_pubkey: &Pubkey,
120 account_program_id: &Pubkey,
121 account_hash: Hash,
122 lamports: u64,
123 to: &Pubkey,
124 ) -> Self {
125 BudgetExpr::After(
126 Condition::AccountData(AccountConstraints {
127 key: *account_pubkey,
128 program_id: *account_program_id,
129 data_hash: account_hash,
130 }),
131 Box::new(Self::new_payment(lamports, to)),
132 )
133 }
134
135 pub fn new_cancelable_authorized_payment(
138 witness: &Pubkey,
139 lamports: u64,
140 to: &Pubkey,
141 from: Option<Pubkey>,
142 ) -> Self {
143 if from.is_none() {
144 return Self::new_authorized_payment(witness, lamports, to);
145 }
146 let from = from.unwrap();
147 BudgetExpr::Or(
148 (
149 Condition::Signature(*witness),
150 Box::new(BudgetExpr::new_payment(lamports, to)),
151 ),
152 (
153 Condition::Signature(from),
154 Box::new(BudgetExpr::new_payment(lamports, &from)),
155 ),
156 )
157 }
158
159 pub fn new_2_2_multisig_payment(
161 from0: &Pubkey,
162 from1: &Pubkey,
163 lamports: u64,
164 to: &Pubkey,
165 ) -> Self {
166 BudgetExpr::And(
167 Condition::Signature(*from0),
168 Condition::Signature(*from1),
169 Box::new(Self::new_payment(lamports, to)),
170 )
171 }
172
173 pub fn new_future_payment(
176 dt: DateTime<Utc>,
177 dt_pubkey: &Pubkey,
178 lamports: u64,
179 to: &Pubkey,
180 ) -> Self {
181 BudgetExpr::After(
182 Condition::Timestamp(dt, *dt_pubkey),
183 Box::new(Self::new_payment(lamports, to)),
184 )
185 }
186
187 pub fn new_cancelable_future_payment(
190 dt: DateTime<Utc>,
191 dt_pubkey: &Pubkey,
192 lamports: u64,
193 to: &Pubkey,
194 from: Option<Pubkey>,
195 ) -> Self {
196 if from.is_none() {
197 return Self::new_future_payment(dt, dt_pubkey, lamports, to);
198 }
199 let from = from.unwrap();
200 BudgetExpr::Or(
201 (
202 Condition::Timestamp(dt, *dt_pubkey),
203 Box::new(Self::new_payment(lamports, to)),
204 ),
205 (
206 Condition::Signature(from),
207 Box::new(Self::new_payment(lamports, &from)),
208 ),
209 )
210 }
211
212 pub fn final_payment(&self) -> Option<Payment> {
214 match self {
215 BudgetExpr::Pay(payment) => Some(payment.clone()),
216 _ => None,
217 }
218 }
219
220 pub fn verify(&self, spendable_lamports: u64) -> bool {
222 match self {
223 BudgetExpr::Pay(payment) => payment.lamports == spendable_lamports,
224 BudgetExpr::After(_, sub_expr) | BudgetExpr::And(_, _, sub_expr) => {
225 sub_expr.verify(spendable_lamports)
226 }
227 BudgetExpr::Or(a, b) => {
228 a.1.verify(spendable_lamports) && b.1.verify(spendable_lamports)
229 }
230 }
231 }
232
233 pub fn apply_witness(&mut self, witness: &Witness, from: &Pubkey) {
236 let new_expr = match self {
237 BudgetExpr::After(cond, sub_expr) if cond.is_satisfied(witness, from) => {
238 Some(sub_expr.clone())
239 }
240 BudgetExpr::Or((cond, sub_expr), _) if cond.is_satisfied(witness, from) => {
241 Some(sub_expr.clone())
242 }
243 BudgetExpr::Or(_, (cond, sub_expr)) if cond.is_satisfied(witness, from) => {
244 Some(sub_expr.clone())
245 }
246 BudgetExpr::And(cond0, cond1, sub_expr) => {
247 if cond0.is_satisfied(witness, from) {
248 Some(Box::new(BudgetExpr::After(cond1.clone(), sub_expr.clone())))
249 } else if cond1.is_satisfied(witness, from) {
250 Some(Box::new(BudgetExpr::After(cond0.clone(), sub_expr.clone())))
251 } else {
252 None
253 }
254 }
255 _ => None,
256 };
257 if let Some(expr) = new_expr {
258 *self = *expr;
259 }
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_signature_satisfied() {
269 let from = Pubkey::default();
270 assert!(Condition::Signature(from).is_satisfied(&Witness::Signature, &from));
271 }
272
273 #[test]
274 fn test_timestamp_satisfied() {
275 let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
276 let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8);
277 let from = Pubkey::default();
278 assert!(Condition::Timestamp(dt1, from).is_satisfied(&Witness::Timestamp(dt1), &from));
279 assert!(Condition::Timestamp(dt1, from).is_satisfied(&Witness::Timestamp(dt2), &from));
280 assert!(!Condition::Timestamp(dt2, from).is_satisfied(&Witness::Timestamp(dt1), &from));
281 }
282
283 #[test]
284 fn test_verify() {
285 let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
286 let from = Pubkey::default();
287 let to = Pubkey::default();
288 assert!(BudgetExpr::new_payment(42, &to).verify(42));
289 assert!(BudgetExpr::new_authorized_payment(&from, 42, &to).verify(42));
290 assert!(BudgetExpr::new_future_payment(dt, &from, 42, &to).verify(42));
291 assert!(
292 BudgetExpr::new_cancelable_future_payment(dt, &from, 42, &to, Some(from)).verify(42)
293 );
294 }
295
296 #[test]
297 fn test_authorized_payment() {
298 let from = Pubkey::default();
299 let to = Pubkey::default();
300
301 let mut expr = BudgetExpr::new_authorized_payment(&from, 42, &to);
302 expr.apply_witness(&Witness::Signature, &from);
303 assert_eq!(expr, BudgetExpr::new_payment(42, &to));
304 }
305
306 #[test]
307 fn test_future_payment() {
308 let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
309 let from = solana_sdk::pubkey::new_rand();
310 let to = solana_sdk::pubkey::new_rand();
311
312 let mut expr = BudgetExpr::new_future_payment(dt, &from, 42, &to);
313 expr.apply_witness(&Witness::Timestamp(dt), &from);
314 assert_eq!(expr, BudgetExpr::new_payment(42, &to));
315 }
316
317 #[test]
318 fn test_unauthorized_future_payment() {
319 let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
322 let from = solana_sdk::pubkey::new_rand();
323 let to = solana_sdk::pubkey::new_rand();
324
325 let mut expr = BudgetExpr::new_future_payment(dt, &from, 42, &to);
326 let orig_expr = expr.clone();
327 expr.apply_witness(&Witness::Timestamp(dt), &to); assert_eq!(expr, orig_expr);
329 }
330
331 #[test]
332 fn test_cancelable_future_payment() {
333 let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
334 let from = Pubkey::default();
335 let to = Pubkey::default();
336
337 let mut expr = BudgetExpr::new_cancelable_future_payment(dt, &from, 42, &to, Some(from));
338 expr.apply_witness(&Witness::Timestamp(dt), &from);
339 assert_eq!(expr, BudgetExpr::new_payment(42, &to));
340
341 let mut expr = BudgetExpr::new_cancelable_future_payment(dt, &from, 42, &to, Some(from));
342 expr.apply_witness(&Witness::Signature, &from);
343 assert_eq!(expr, BudgetExpr::new_payment(42, &from));
344 }
345 #[test]
346 fn test_2_2_multisig_payment() {
347 let from0 = solana_sdk::pubkey::new_rand();
348 let from1 = solana_sdk::pubkey::new_rand();
349 let to = Pubkey::default();
350
351 let mut expr = BudgetExpr::new_2_2_multisig_payment(&from0, &from1, 42, &to);
352 expr.apply_witness(&Witness::Signature, &from0);
353 assert_eq!(expr, BudgetExpr::new_authorized_payment(&from1, 42, &to));
354 }
355
356 #[test]
357 fn test_multisig_after_sig() {
358 let from0 = solana_sdk::pubkey::new_rand();
359 let from1 = solana_sdk::pubkey::new_rand();
360 let from2 = solana_sdk::pubkey::new_rand();
361 let to = Pubkey::default();
362
363 let expr = BudgetExpr::new_2_2_multisig_payment(&from0, &from1, 42, &to);
364 let mut expr = BudgetExpr::After(Condition::Signature(from2), Box::new(expr));
365
366 expr.apply_witness(&Witness::Signature, &from2);
367 expr.apply_witness(&Witness::Signature, &from0);
368 assert_eq!(expr, BudgetExpr::new_authorized_payment(&from1, 42, &to));
369 }
370
371 #[test]
372 fn test_multisig_after_ts() {
373 let from0 = solana_sdk::pubkey::new_rand();
374 let from1 = solana_sdk::pubkey::new_rand();
375 let dt = Utc.ymd(2014, 11, 11).and_hms(7, 7, 7);
376 let to = Pubkey::default();
377
378 let expr = BudgetExpr::new_2_2_multisig_payment(&from0, &from1, 42, &to);
379 let mut expr = BudgetExpr::After(Condition::Timestamp(dt, from0), Box::new(expr));
380
381 expr.apply_witness(&Witness::Timestamp(dt), &from0);
382 assert_eq!(
383 expr,
384 BudgetExpr::new_2_2_multisig_payment(&from0, &from1, 42, &to)
385 );
386
387 expr.apply_witness(&Witness::Signature, &from0);
388 assert_eq!(expr, BudgetExpr::new_authorized_payment(&from1, 42, &to));
389 }
390}