1use crate::{
3 budget_expr::Witness,
4 budget_instruction::{BudgetError, BudgetInstruction},
5 budget_state::BudgetState,
6};
7use chrono::prelude::{DateTime, Utc};
8use log::*;
9use solana_sdk::{
10 account::{ReadableAccount, WritableAccount},
11 hash::hash,
12 instruction::InstructionError,
13 keyed_account::{keyed_account_at_index, KeyedAccount},
14 process_instruction::InvokeContext,
15 program_utils::limited_deserialize,
16 pubkey::Pubkey,
17};
18
19fn apply_signature(
22 budget_state: &mut BudgetState,
23 witness_keyed_account: &KeyedAccount,
24 contract_keyed_account: &KeyedAccount,
25 to_keyed_account: Result<&KeyedAccount, InstructionError>,
26) -> Result<(), InstructionError> {
27 let mut final_payment = None;
28 if let Some(ref mut expr) = budget_state.pending_budget {
29 let key = witness_keyed_account.signer_key().unwrap();
30 expr.apply_witness(&Witness::Signature, key);
31 final_payment = expr.final_payment();
32 }
33
34 if let Some(payment) = final_payment {
35 if let Some(key) = witness_keyed_account.signer_key() {
36 if &payment.to == key {
37 budget_state.pending_budget = None;
38 contract_keyed_account
39 .try_account_ref_mut()?
40 .checked_sub_lamports(payment.lamports)?;
41 witness_keyed_account
42 .try_account_ref_mut()?
43 .checked_add_lamports(payment.lamports)?;
44 return Ok(());
45 }
46 }
47 let to_keyed_account = to_keyed_account?;
48 if &payment.to != to_keyed_account.unsigned_key() {
49 trace!("destination missing");
50 return Err(BudgetError::DestinationMissing.into());
51 }
52 budget_state.pending_budget = None;
53 contract_keyed_account
54 .try_account_ref_mut()?
55 .checked_sub_lamports(payment.lamports)?;
56 to_keyed_account
57 .try_account_ref_mut()?
58 .checked_add_lamports(payment.lamports)?;
59 }
60 Ok(())
61}
62
63fn apply_timestamp(
66 budget_state: &mut BudgetState,
67 witness_keyed_account: &KeyedAccount,
68 contract_keyed_account: &KeyedAccount,
69 to_keyed_account: Result<&KeyedAccount, InstructionError>,
70 dt: DateTime<Utc>,
71) -> Result<(), InstructionError> {
72 let mut final_payment = None;
74
75 if let Some(ref mut expr) = budget_state.pending_budget {
76 let key = witness_keyed_account.signer_key().unwrap();
77 expr.apply_witness(&Witness::Timestamp(dt), key);
78 final_payment = expr.final_payment();
79 }
80
81 if let Some(payment) = final_payment {
82 let to_keyed_account = to_keyed_account?;
83 if &payment.to != to_keyed_account.unsigned_key() {
84 trace!("destination missing");
85 return Err(BudgetError::DestinationMissing.into());
86 }
87 budget_state.pending_budget = None;
88 contract_keyed_account
89 .try_account_ref_mut()?
90 .checked_sub_lamports(payment.lamports)?;
91 to_keyed_account
92 .try_account_ref_mut()?
93 .checked_add_lamports(payment.lamports)?;
94 }
95 Ok(())
96}
97
98fn apply_account_data(
100 budget_state: &mut BudgetState,
101 witness_keyed_account: &KeyedAccount,
102 contract_keyed_account: &KeyedAccount,
103 to_keyed_account: Result<&KeyedAccount, InstructionError>,
104) -> Result<(), InstructionError> {
105 let mut final_payment = None;
107
108 if let Some(ref mut expr) = budget_state.pending_budget {
109 let key = witness_keyed_account.unsigned_key();
110 let program_id = witness_keyed_account.owner()?;
111 let actual_hash = hash(&witness_keyed_account.try_account_ref()?.data());
112 expr.apply_witness(&Witness::AccountData(actual_hash, program_id), key);
113 final_payment = expr.final_payment();
114 }
115
116 if let Some(payment) = final_payment {
117 let to_keyed_account = to_keyed_account?;
118 if &payment.to != to_keyed_account.unsigned_key() {
119 trace!("destination missing");
120 return Err(BudgetError::DestinationMissing.into());
121 }
122 budget_state.pending_budget = None;
123 contract_keyed_account
124 .try_account_ref_mut()?
125 .checked_sub_lamports(payment.lamports)?;
126 to_keyed_account
127 .try_account_ref_mut()?
128 .checked_add_lamports(payment.lamports)?;
129 }
130 Ok(())
131}
132
133pub fn process_instruction(
134 _program_id: &Pubkey,
135 data: &[u8],
136 invoke_context: &mut dyn InvokeContext,
137) -> Result<(), InstructionError> {
138 let keyed_accounts = invoke_context.get_keyed_accounts()?;
139
140 let instruction = limited_deserialize(data)?;
141
142 trace!("process_instruction: {:?}", instruction);
143
144 match instruction {
145 BudgetInstruction::InitializeAccount(expr) => {
146 let contract_keyed_account = keyed_account_at_index(keyed_accounts, 0)?;
147
148 if let Some(payment) = expr.final_payment() {
149 let to_keyed_account = contract_keyed_account;
150 let contract_keyed_account = keyed_account_at_index(keyed_accounts, 1)?;
151 contract_keyed_account
152 .try_account_ref_mut()?
153 .set_lamports(0);
154 to_keyed_account
155 .try_account_ref_mut()?
156 .checked_add_lamports(payment.lamports)?;
157 return Ok(());
158 }
159 let existing =
160 BudgetState::deserialize(&contract_keyed_account.try_account_ref_mut()?.data())
161 .ok();
162 if Some(true) == existing.map(|x| x.initialized) {
163 trace!("contract already exists");
164 return Err(InstructionError::AccountAlreadyInitialized);
165 }
166 let budget_state = BudgetState {
167 pending_budget: Some(*expr),
168 initialized: true,
169 };
170 budget_state.serialize(
171 &mut contract_keyed_account
172 .try_account_ref_mut()?
173 .data_as_mut_slice(),
174 )
175 }
176 BudgetInstruction::ApplyTimestamp(dt) => {
177 let witness_keyed_account = keyed_account_at_index(keyed_accounts, 0)?;
178 let contract_keyed_account = keyed_account_at_index(keyed_accounts, 1)?;
179 let mut budget_state =
180 BudgetState::deserialize(&contract_keyed_account.try_account_ref()?.data())?;
181 if !budget_state.is_pending() {
182 return Ok(()); }
184 if !budget_state.initialized {
185 trace!("contract is uninitialized");
186 return Err(InstructionError::UninitializedAccount);
187 }
188 if witness_keyed_account.signer_key().is_none() {
189 return Err(InstructionError::MissingRequiredSignature);
190 }
191 trace!("apply timestamp");
192 apply_timestamp(
193 &mut budget_state,
194 witness_keyed_account,
195 contract_keyed_account,
196 keyed_account_at_index(keyed_accounts, 2),
197 dt,
198 )?;
199 trace!("apply timestamp committed");
200 budget_state.serialize(
201 &mut contract_keyed_account
202 .try_account_ref_mut()?
203 .data_as_mut_slice(),
204 )
205 }
206 BudgetInstruction::ApplySignature => {
207 let witness_keyed_account = keyed_account_at_index(keyed_accounts, 0)?;
208 let contract_keyed_account = keyed_account_at_index(keyed_accounts, 1)?;
209 let mut budget_state =
210 BudgetState::deserialize(&contract_keyed_account.try_account_ref()?.data())?;
211 if !budget_state.is_pending() {
212 return Ok(()); }
214 if !budget_state.initialized {
215 trace!("contract is uninitialized");
216 return Err(InstructionError::UninitializedAccount);
217 }
218 if witness_keyed_account.signer_key().is_none() {
219 return Err(InstructionError::MissingRequiredSignature);
220 }
221 trace!("apply signature");
222 apply_signature(
223 &mut budget_state,
224 witness_keyed_account,
225 contract_keyed_account,
226 keyed_account_at_index(keyed_accounts, 2),
227 )?;
228 trace!("apply signature committed");
229 budget_state.serialize(
230 &mut contract_keyed_account
231 .try_account_ref_mut()?
232 .data_as_mut_slice(),
233 )
234 }
235 BudgetInstruction::ApplyAccountData => {
236 let witness_keyed_account = keyed_account_at_index(keyed_accounts, 0)?;
237 let contract_keyed_account = keyed_account_at_index(keyed_accounts, 1)?;
238 let mut budget_state =
239 BudgetState::deserialize(&contract_keyed_account.try_account_ref()?.data())?;
240 if !budget_state.is_pending() {
241 return Ok(()); }
243 if !budget_state.initialized {
244 trace!("contract is uninitialized");
245 return Err(InstructionError::UninitializedAccount);
246 }
247 apply_account_data(
248 &mut budget_state,
249 witness_keyed_account,
250 contract_keyed_account,
251 keyed_account_at_index(keyed_accounts, 2),
252 )?;
253 trace!("apply account data committed");
254 budget_state.serialize(
255 &mut contract_keyed_account
256 .try_account_ref_mut()?
257 .data_as_mut_slice(),
258 )
259 }
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266 use crate::budget_instruction;
267 use crate::id;
268 use solana_runtime::bank::Bank;
269 use solana_runtime::bank_client::BankClient;
270 use solana_sdk::account::{Account, AccountSharedData};
271 use solana_sdk::client::SyncClient;
272 use solana_sdk::genesis_config::create_genesis_config;
273 use solana_sdk::hash::hash;
274 use solana_sdk::instruction::InstructionError;
275 use solana_sdk::message::Message;
276 use solana_sdk::signature::{Keypair, Signer};
277 use solana_sdk::transaction::TransactionError;
278
279 fn create_bank(lamports: u64) -> (Bank, Keypair) {
280 let (genesis_config, mint_keypair) = create_genesis_config(lamports);
281 let mut bank = Bank::new(&genesis_config);
282 bank.add_builtin("budget_program", id(), process_instruction);
283 (bank, mint_keypair)
284 }
285
286 #[test]
287 fn test_initialize_no_panic() {
288 let (bank, alice_keypair) = create_bank(1);
289 let bank_client = BankClient::new(bank);
290
291 let alice_pubkey = alice_keypair.pubkey();
292 let budget_keypair = Keypair::new();
293 let budget_pubkey = budget_keypair.pubkey();
294 let bob_pubkey = solana_sdk::pubkey::new_rand();
295
296 let mut instructions =
297 budget_instruction::payment(&alice_pubkey, &bob_pubkey, &budget_pubkey, 1);
298 instructions[1].accounts = vec![]; let message = Message::new(&instructions, Some(&alice_pubkey));
301 assert_eq!(
302 bank_client
303 .send_and_confirm_message(&[&alice_keypair, &budget_keypair], message)
304 .unwrap_err()
305 .unwrap(),
306 TransactionError::InstructionError(1, InstructionError::NotEnoughAccountKeys)
307 );
308 }
309
310 #[test]
311 fn test_budget_payment() {
312 let (bank, alice_keypair) = create_bank(10_000);
313 let bank_client = BankClient::new(bank);
314 let alice_pubkey = alice_keypair.pubkey();
315 let bob_pubkey = solana_sdk::pubkey::new_rand();
316 let budget_keypair = Keypair::new();
317 let budget_pubkey = budget_keypair.pubkey();
318 let instructions =
319 budget_instruction::payment(&alice_pubkey, &bob_pubkey, &budget_pubkey, 100);
320 let message = Message::new(&instructions, Some(&alice_pubkey));
321 bank_client
322 .send_and_confirm_message(&[&alice_keypair, &budget_keypair], message)
323 .unwrap();
324 assert_eq!(bank_client.get_balance(&bob_pubkey).unwrap(), 100);
325 }
326
327 #[test]
328 fn test_unsigned_witness_key() {
329 let (bank, alice_keypair) = create_bank(10_000);
330 let bank_client = BankClient::new(bank);
331 let alice_pubkey = alice_keypair.pubkey();
332
333 let budget_keypair = Keypair::new();
335 let budget_pubkey = budget_keypair.pubkey();
336 let bob_pubkey = solana_sdk::pubkey::new_rand();
337 let witness = solana_sdk::pubkey::new_rand();
338 let instructions = budget_instruction::when_signed(
339 &alice_pubkey,
340 &bob_pubkey,
341 &budget_pubkey,
342 &witness,
343 None,
344 1,
345 );
346 let message = Message::new(&instructions, Some(&alice_pubkey));
347 bank_client
348 .send_and_confirm_message(&[&alice_keypair, &budget_keypair], message)
349 .unwrap();
350
351 let mallory_keypair = Keypair::new();
353 let mallory_pubkey = mallory_keypair.pubkey();
354 bank_client
355 .transfer_and_confirm(1, &alice_keypair, &mallory_pubkey)
356 .unwrap();
357 let instruction =
358 budget_instruction::apply_signature(&mallory_pubkey, &budget_pubkey, &bob_pubkey);
359 let mut message = Message::new(&[instruction], Some(&mallory_pubkey));
360
361 message.account_keys.insert(3, alice_pubkey);
363 message.instructions[0].accounts[0] = 3;
364 message.instructions[0].program_id_index = 4;
365
366 assert_eq!(
368 bank_client
369 .send_and_confirm_message(&[&mallory_keypair], message)
370 .unwrap_err()
371 .unwrap(),
372 TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
373 );
374 }
375
376 #[test]
377 fn test_unsigned_timestamp() {
378 let (bank, alice_keypair) = create_bank(10_000);
379 let bank_client = BankClient::new(bank);
380 let alice_pubkey = alice_keypair.pubkey();
381
382 let budget_keypair = Keypair::new();
384 let budget_pubkey = budget_keypair.pubkey();
385 let bob_pubkey = solana_sdk::pubkey::new_rand();
386 let dt = Utc::now();
387 let instructions = budget_instruction::on_date(
388 &alice_pubkey,
389 &bob_pubkey,
390 &budget_pubkey,
391 dt,
392 &alice_pubkey,
393 None,
394 1,
395 );
396 let message = Message::new(&instructions, Some(&alice_pubkey));
397 bank_client
398 .send_and_confirm_message(&[&alice_keypair, &budget_keypair], message)
399 .unwrap();
400
401 let mallory_keypair = Keypair::new();
403 let mallory_pubkey = mallory_keypair.pubkey();
404 bank_client
405 .transfer_and_confirm(1, &alice_keypair, &mallory_pubkey)
406 .unwrap();
407 let instruction =
408 budget_instruction::apply_timestamp(&mallory_pubkey, &budget_pubkey, &bob_pubkey, dt);
409 let mut message = Message::new(&[instruction], Some(&mallory_pubkey));
410
411 message.account_keys.insert(3, alice_pubkey);
413 message.instructions[0].accounts[0] = 3;
414 message.instructions[0].program_id_index = 4;
415
416 assert_eq!(
418 bank_client
419 .send_and_confirm_message(&[&mallory_keypair], message)
420 .unwrap_err()
421 .unwrap(),
422 TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
423 );
424 }
425
426 #[test]
427 fn test_pay_on_date() {
428 let (bank, alice_keypair) = create_bank(2);
429 let bank_client = BankClient::new(bank);
430 let alice_pubkey = alice_keypair.pubkey();
431 let budget_keypair = Keypair::new();
432 let budget_pubkey = budget_keypair.pubkey();
433 let bob_pubkey = solana_sdk::pubkey::new_rand();
434 let mallory_pubkey = solana_sdk::pubkey::new_rand();
435 let dt = Utc::now();
436
437 let instructions = budget_instruction::on_date(
438 &alice_pubkey,
439 &bob_pubkey,
440 &budget_pubkey,
441 dt,
442 &alice_pubkey,
443 None,
444 1,
445 );
446 let message = Message::new(&instructions, Some(&alice_pubkey));
447 bank_client
448 .send_and_confirm_message(&[&alice_keypair, &budget_keypair], message)
449 .unwrap();
450 assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 1);
451 assert_eq!(bank_client.get_balance(&budget_pubkey).unwrap(), 1);
452
453 let contract_account = bank_client
454 .get_account_data(&budget_pubkey)
455 .unwrap()
456 .unwrap();
457 let budget_state = BudgetState::deserialize(&contract_account).unwrap();
458 assert!(budget_state.is_pending());
459
460 let instruction =
462 budget_instruction::apply_timestamp(&alice_pubkey, &budget_pubkey, &mallory_pubkey, dt);
463 assert_eq!(
464 bank_client
465 .send_and_confirm_instruction(&alice_keypair, instruction)
466 .unwrap_err()
467 .unwrap(),
468 TransactionError::InstructionError(
469 0,
470 InstructionError::Custom(BudgetError::DestinationMissing as u32)
471 )
472 );
473 assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 1);
474 assert_eq!(bank_client.get_balance(&budget_pubkey).unwrap(), 1);
475 assert_eq!(bank_client.get_balance(&bob_pubkey).unwrap(), 0);
476
477 let contract_account = bank_client
478 .get_account_data(&budget_pubkey)
479 .unwrap()
480 .unwrap();
481 let budget_state = BudgetState::deserialize(&contract_account).unwrap();
482 assert!(budget_state.is_pending());
483
484 let instruction =
487 budget_instruction::apply_timestamp(&alice_pubkey, &budget_pubkey, &bob_pubkey, dt);
488 bank_client
489 .send_and_confirm_instruction(&alice_keypair, instruction)
490 .unwrap();
491 assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 1);
492 assert_eq!(bank_client.get_balance(&budget_pubkey).unwrap(), 0);
493 assert_eq!(bank_client.get_balance(&bob_pubkey).unwrap(), 1);
494 assert_eq!(bank_client.get_account_data(&budget_pubkey).unwrap(), None);
495 }
496
497 #[test]
498 fn test_cancel_payment() {
499 let (bank, alice_keypair) = create_bank(3);
500 let bank_client = BankClient::new(bank);
501 let alice_pubkey = alice_keypair.pubkey();
502 let budget_keypair = Keypair::new();
503 let budget_pubkey = budget_keypair.pubkey();
504 let bob_pubkey = solana_sdk::pubkey::new_rand();
505 let dt = Utc::now();
506
507 let instructions = budget_instruction::on_date(
508 &alice_pubkey,
509 &bob_pubkey,
510 &budget_pubkey,
511 dt,
512 &alice_pubkey,
513 Some(alice_pubkey),
514 1,
515 );
516 let message = Message::new(&instructions, Some(&alice_pubkey));
517 bank_client
518 .send_and_confirm_message(&[&alice_keypair, &budget_keypair], message)
519 .unwrap();
520 assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 2);
521 assert_eq!(bank_client.get_balance(&budget_pubkey).unwrap(), 1);
522
523 let contract_account = bank_client
524 .get_account_data(&budget_pubkey)
525 .unwrap()
526 .unwrap();
527 let budget_state = BudgetState::deserialize(&contract_account).unwrap();
528 assert!(budget_state.is_pending());
529
530 let mallory_keypair = Keypair::new();
532 let mallory_pubkey = mallory_keypair.pubkey();
533 bank_client
534 .transfer_and_confirm(1, &alice_keypair, &mallory_pubkey)
535 .unwrap();
536 assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 1);
537
538 let instruction =
539 budget_instruction::apply_signature(&mallory_pubkey, &budget_pubkey, &bob_pubkey);
540 bank_client
541 .send_and_confirm_instruction(&mallory_keypair, instruction)
542 .unwrap();
543 assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 1);
545 assert_eq!(bank_client.get_balance(&budget_pubkey).unwrap(), 1);
546 assert_eq!(bank_client.get_account_data(&bob_pubkey).unwrap(), None);
547
548 let instruction =
550 budget_instruction::apply_signature(&alice_pubkey, &budget_pubkey, &alice_pubkey);
551 bank_client
552 .send_and_confirm_instruction(&alice_keypair, instruction)
553 .unwrap();
554 assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 2);
555 assert_eq!(bank_client.get_account_data(&budget_pubkey).unwrap(), None);
556 assert_eq!(bank_client.get_account_data(&bob_pubkey).unwrap(), None);
557 }
558
559 #[test]
560 fn test_pay_when_account_data() {
561 let (bank, alice_keypair) = create_bank(42);
562 let game_pubkey = solana_sdk::pubkey::new_rand();
563 let game_account = AccountSharedData::from(Account {
564 lamports: 1,
565 data: vec![1, 2, 3],
566 ..Account::default()
567 });
568 bank.store_account(&game_pubkey, &game_account);
569 assert_eq!(
570 bank.get_account(&game_pubkey).unwrap().data(),
571 &vec![1, 2, 3]
572 );
573
574 let bank_client = BankClient::new(bank);
575
576 let alice_pubkey = alice_keypair.pubkey();
577 let game_hash = hash(&[1, 2, 3]);
578 let budget_keypair = Keypair::new();
579 let budget_pubkey = budget_keypair.pubkey();
580 let bob_keypair = Keypair::new();
581 let bob_pubkey = bob_keypair.pubkey();
582
583 bank_client
585 .transfer_and_confirm(1, &alice_keypair, &bob_pubkey)
586 .unwrap();
587
588 let instructions = budget_instruction::when_account_data(
589 &alice_pubkey,
590 &bob_pubkey,
591 &budget_pubkey,
592 &game_pubkey,
593 game_account.owner(),
594 game_hash,
595 41,
596 );
597 let message = Message::new(&instructions, Some(&alice_pubkey));
598 bank_client
599 .send_and_confirm_message(&[&alice_keypair, &budget_keypair], message)
600 .unwrap();
601 assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 0);
602 assert_eq!(bank_client.get_balance(&budget_pubkey).unwrap(), 41);
603
604 let contract_account = bank_client
605 .get_account_data(&budget_pubkey)
606 .unwrap()
607 .unwrap();
608 let budget_state = BudgetState::deserialize(&contract_account).unwrap();
609 assert!(budget_state.is_pending());
610
611 let instruction =
613 budget_instruction::apply_account_data(&game_pubkey, &budget_pubkey, &bob_pubkey);
614
615 let message = Message::new(&[instruction], Some(&bob_pubkey));
618 bank_client
619 .send_and_confirm_message(&[&bob_keypair], message)
620 .unwrap();
621
622 assert_eq!(bank_client.get_balance(&alice_pubkey).unwrap(), 0);
623 assert_eq!(bank_client.get_balance(&budget_pubkey).unwrap(), 0);
624 assert_eq!(bank_client.get_balance(&bob_pubkey).unwrap(), 42);
625 assert_eq!(bank_client.get_account_data(&budget_pubkey).unwrap(), None);
626 }
627}