2018-09-26 09:33:52 -07:00
|
|
|
//! The `budget_transaction` module provides functionality for creating Budget transactions.
|
|
|
|
|
2018-12-14 20:39:10 -08:00
|
|
|
use crate::budget_expr::{BudgetExpr, Condition};
|
|
|
|
use crate::budget_instruction::Instruction;
|
|
|
|
use crate::budget_program;
|
|
|
|
use crate::hash::Hash;
|
|
|
|
use crate::payment_plan::Payment;
|
|
|
|
use crate::pubkey::Pubkey;
|
|
|
|
use crate::signature::{Keypair, KeypairUtil};
|
|
|
|
use crate::system_instruction::SystemInstruction;
|
|
|
|
use crate::system_program;
|
|
|
|
use crate::transaction::{self, Transaction};
|
2018-11-06 05:50:00 -08:00
|
|
|
use bincode::deserialize;
|
2018-09-26 09:33:52 -07:00
|
|
|
use chrono::prelude::*;
|
|
|
|
|
2019-02-01 07:36:35 -08:00
|
|
|
pub struct BudgetTransaction {}
|
2018-09-26 09:33:52 -07:00
|
|
|
|
2019-02-01 07:36:35 -08:00
|
|
|
impl BudgetTransaction {
|
2018-09-26 09:33:52 -07:00
|
|
|
/// Create and sign a new Transaction. Used for unit-testing.
|
2019-02-01 07:36:35 -08:00
|
|
|
pub fn new_payment(
|
2018-09-26 09:33:52 -07:00
|
|
|
from_keypair: &Keypair,
|
|
|
|
to: Pubkey,
|
2018-11-05 08:36:22 -08:00
|
|
|
tokens: u64,
|
2018-09-26 09:33:52 -07:00
|
|
|
last_id: Hash,
|
2019-02-01 07:36:35 -08:00
|
|
|
fee: u64,
|
|
|
|
) -> Transaction {
|
2018-10-17 22:21:19 -07:00
|
|
|
let contract = Keypair::new().pubkey();
|
|
|
|
let keys = vec![from_keypair.pubkey(), contract];
|
|
|
|
|
2018-11-16 08:04:46 -08:00
|
|
|
let system_instruction = SystemInstruction::Move { tokens };
|
2018-10-17 22:21:19 -07:00
|
|
|
|
2018-09-26 09:33:52 -07:00
|
|
|
let payment = Payment {
|
|
|
|
tokens: tokens - fee,
|
|
|
|
to,
|
|
|
|
};
|
2018-11-02 19:13:33 -07:00
|
|
|
let budget_instruction = Instruction::NewBudget(BudgetExpr::Pay(payment));
|
2018-10-17 22:21:19 -07:00
|
|
|
|
2018-12-03 13:32:31 -08:00
|
|
|
let program_ids = vec![system_program::id(), budget_program::id()];
|
2018-10-17 22:21:19 -07:00
|
|
|
|
|
|
|
let instructions = vec![
|
2018-11-06 05:50:00 -08:00
|
|
|
transaction::Instruction::new(0, &system_instruction, vec![0, 1]),
|
|
|
|
transaction::Instruction::new(1, &budget_instruction, vec![1]),
|
2018-10-17 22:21:19 -07:00
|
|
|
];
|
|
|
|
|
2019-02-01 07:36:35 -08:00
|
|
|
Transaction::new_with_instructions(
|
2018-10-26 14:43:34 -07:00
|
|
|
&[from_keypair],
|
|
|
|
&keys,
|
|
|
|
last_id,
|
|
|
|
fee,
|
|
|
|
program_ids,
|
|
|
|
instructions,
|
|
|
|
)
|
2018-09-26 09:33:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Create and sign a new Transaction. Used for unit-testing.
|
2019-02-01 07:36:35 -08:00
|
|
|
#[allow(clippy::new_ret_no_self)]
|
|
|
|
pub fn new(from_keypair: &Keypair, to: Pubkey, tokens: u64, last_id: Hash) -> Transaction {
|
|
|
|
Self::new_payment(from_keypair, to, tokens, last_id, 0)
|
2018-09-26 09:33:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Create and sign a new Witness Timestamp. Used for unit-testing.
|
2019-02-01 07:36:35 -08:00
|
|
|
pub fn new_timestamp(
|
2018-09-26 09:33:52 -07:00
|
|
|
from_keypair: &Keypair,
|
|
|
|
contract: Pubkey,
|
|
|
|
to: Pubkey,
|
|
|
|
dt: DateTime<Utc>,
|
|
|
|
last_id: Hash,
|
2019-02-01 07:36:35 -08:00
|
|
|
) -> Transaction {
|
2018-09-26 09:33:52 -07:00
|
|
|
let instruction = Instruction::ApplyTimestamp(dt);
|
2019-02-01 07:36:35 -08:00
|
|
|
Transaction::new(
|
2018-09-26 09:33:52 -07:00
|
|
|
from_keypair,
|
|
|
|
&[contract, to],
|
2018-11-23 12:45:34 -08:00
|
|
|
budget_program::id(),
|
2018-11-06 05:50:00 -08:00
|
|
|
&instruction,
|
2018-09-26 09:33:52 -07:00
|
|
|
last_id,
|
|
|
|
0,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create and sign a new Witness Signature. Used for unit-testing.
|
2019-02-01 07:36:35 -08:00
|
|
|
pub fn new_signature(
|
2018-09-26 09:33:52 -07:00
|
|
|
from_keypair: &Keypair,
|
|
|
|
contract: Pubkey,
|
|
|
|
to: Pubkey,
|
|
|
|
last_id: Hash,
|
2019-02-01 07:36:35 -08:00
|
|
|
) -> Transaction {
|
2018-09-26 09:33:52 -07:00
|
|
|
let instruction = Instruction::ApplySignature;
|
2019-02-07 11:14:10 -08:00
|
|
|
let mut keys = vec![contract];
|
|
|
|
if from_keypair.pubkey() != to {
|
|
|
|
keys.push(to);
|
|
|
|
}
|
2019-02-01 07:36:35 -08:00
|
|
|
Transaction::new(
|
2018-09-26 09:33:52 -07:00
|
|
|
from_keypair,
|
2019-02-07 11:14:10 -08:00
|
|
|
&keys,
|
2018-11-23 12:45:34 -08:00
|
|
|
budget_program::id(),
|
2018-11-06 05:50:00 -08:00
|
|
|
&instruction,
|
2018-09-26 09:33:52 -07:00
|
|
|
last_id,
|
|
|
|
0,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create and sign a postdated Transaction. Used for unit-testing.
|
2019-02-01 07:36:35 -08:00
|
|
|
pub fn new_on_date(
|
2018-09-26 09:33:52 -07:00
|
|
|
from_keypair: &Keypair,
|
|
|
|
to: Pubkey,
|
|
|
|
contract: Pubkey,
|
|
|
|
dt: DateTime<Utc>,
|
|
|
|
dt_pubkey: Pubkey,
|
|
|
|
cancelable: Option<Pubkey>,
|
2018-11-05 08:36:22 -08:00
|
|
|
tokens: u64,
|
2018-09-26 09:33:52 -07:00
|
|
|
last_id: Hash,
|
2019-02-01 07:36:35 -08:00
|
|
|
) -> Transaction {
|
2018-11-02 19:13:33 -07:00
|
|
|
let expr = if let Some(from) = cancelable {
|
|
|
|
BudgetExpr::Or(
|
2019-01-16 16:51:50 -08:00
|
|
|
(
|
|
|
|
Condition::Timestamp(dt, dt_pubkey),
|
|
|
|
Box::new(BudgetExpr::new_payment(tokens, to)),
|
|
|
|
),
|
|
|
|
(
|
|
|
|
Condition::Signature(from),
|
|
|
|
Box::new(BudgetExpr::new_payment(tokens, from)),
|
|
|
|
),
|
2018-09-26 09:33:52 -07:00
|
|
|
)
|
|
|
|
} else {
|
2019-01-16 16:51:50 -08:00
|
|
|
BudgetExpr::After(
|
|
|
|
Condition::Timestamp(dt, dt_pubkey),
|
|
|
|
Box::new(BudgetExpr::new_payment(tokens, to)),
|
|
|
|
)
|
2018-09-26 09:33:52 -07:00
|
|
|
};
|
2018-11-02 19:13:33 -07:00
|
|
|
let instruction = Instruction::NewBudget(expr);
|
2019-02-01 07:36:35 -08:00
|
|
|
Transaction::new(
|
2018-09-26 09:33:52 -07:00
|
|
|
from_keypair,
|
|
|
|
&[contract],
|
2018-11-23 12:45:34 -08:00
|
|
|
budget_program::id(),
|
2018-11-06 05:50:00 -08:00
|
|
|
&instruction,
|
2018-09-26 09:33:52 -07:00
|
|
|
last_id,
|
|
|
|
0,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
/// Create and sign a multisig Transaction.
|
2019-02-01 07:36:35 -08:00
|
|
|
pub fn new_when_signed(
|
2018-09-26 09:33:52 -07:00
|
|
|
from_keypair: &Keypair,
|
|
|
|
to: Pubkey,
|
|
|
|
contract: Pubkey,
|
|
|
|
witness: Pubkey,
|
|
|
|
cancelable: Option<Pubkey>,
|
2018-11-05 08:36:22 -08:00
|
|
|
tokens: u64,
|
2018-09-26 09:33:52 -07:00
|
|
|
last_id: Hash,
|
2019-02-01 07:36:35 -08:00
|
|
|
) -> Transaction {
|
2018-11-02 19:13:33 -07:00
|
|
|
let expr = if let Some(from) = cancelable {
|
|
|
|
BudgetExpr::Or(
|
2019-01-16 16:51:50 -08:00
|
|
|
(
|
|
|
|
Condition::Signature(witness),
|
|
|
|
Box::new(BudgetExpr::new_payment(tokens, to)),
|
|
|
|
),
|
|
|
|
(
|
|
|
|
Condition::Signature(from),
|
|
|
|
Box::new(BudgetExpr::new_payment(tokens, from)),
|
|
|
|
),
|
2018-09-26 09:33:52 -07:00
|
|
|
)
|
|
|
|
} else {
|
2019-01-16 16:51:50 -08:00
|
|
|
BudgetExpr::After(
|
|
|
|
Condition::Signature(witness),
|
|
|
|
Box::new(BudgetExpr::new_payment(tokens, to)),
|
|
|
|
)
|
2018-09-26 09:33:52 -07:00
|
|
|
};
|
2018-11-02 19:13:33 -07:00
|
|
|
let instruction = Instruction::NewBudget(expr);
|
2019-02-01 07:36:35 -08:00
|
|
|
Transaction::new(
|
2018-09-26 09:33:52 -07:00
|
|
|
from_keypair,
|
|
|
|
&[contract],
|
2018-11-23 12:45:34 -08:00
|
|
|
budget_program::id(),
|
2018-11-06 05:50:00 -08:00
|
|
|
&instruction,
|
2018-09-26 09:33:52 -07:00
|
|
|
last_id,
|
|
|
|
0,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-02-01 07:36:35 -08:00
|
|
|
pub fn system_instruction(tx: &Transaction, index: usize) -> Option<SystemInstruction> {
|
|
|
|
deserialize(&tx.userdata(index)).ok()
|
2018-10-17 22:21:19 -07:00
|
|
|
}
|
|
|
|
|
2019-02-01 07:36:35 -08:00
|
|
|
pub fn instruction(tx: &Transaction, index: usize) -> Option<Instruction> {
|
|
|
|
deserialize(&tx.userdata(index)).ok()
|
2018-09-26 09:33:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Verify only the payment plan.
|
2019-02-01 07:36:35 -08:00
|
|
|
pub fn verify_plan(tx: &Transaction) -> bool {
|
|
|
|
if let Some(SystemInstruction::Move { tokens }) = Self::system_instruction(tx, 0) {
|
|
|
|
if let Some(Instruction::NewBudget(expr)) = BudgetTransaction::instruction(&tx, 1) {
|
|
|
|
if !(tx.fee <= tokens && expr.verify(tokens - tx.fee)) {
|
2018-09-28 16:16:35 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2018-09-26 09:33:52 -07:00
|
|
|
}
|
2018-09-28 16:16:35 -07:00
|
|
|
true
|
2018-09-26 09:33:52 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use bincode::{deserialize, serialize};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_claim() {
|
|
|
|
let keypair = Keypair::new();
|
|
|
|
let zero = Hash::default();
|
2019-02-01 07:36:35 -08:00
|
|
|
let tx0 = BudgetTransaction::new(&keypair, keypair.pubkey(), 42, zero);
|
|
|
|
assert!(BudgetTransaction::verify_plan(&tx0));
|
2018-09-26 09:33:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2019-02-01 07:36:35 -08:00
|
|
|
fn test_payment() {
|
2018-09-26 09:33:52 -07:00
|
|
|
let zero = Hash::default();
|
|
|
|
let keypair0 = Keypair::new();
|
|
|
|
let keypair1 = Keypair::new();
|
|
|
|
let pubkey1 = keypair1.pubkey();
|
2019-02-01 07:36:35 -08:00
|
|
|
let tx0 = BudgetTransaction::new(&keypair0, pubkey1, 42, zero);
|
|
|
|
assert!(BudgetTransaction::verify_plan(&tx0));
|
2018-09-26 09:33:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2019-02-01 07:36:35 -08:00
|
|
|
fn test_payment_with_fee() {
|
2018-09-26 09:33:52 -07:00
|
|
|
let zero = Hash::default();
|
|
|
|
let keypair0 = Keypair::new();
|
|
|
|
let pubkey1 = Keypair::new().pubkey();
|
2019-02-01 07:36:35 -08:00
|
|
|
let tx0 = BudgetTransaction::new_payment(&keypair0, pubkey1, 1, zero, 1);
|
|
|
|
assert!(BudgetTransaction::verify_plan(&tx0));
|
2018-09-26 09:33:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_serialize_claim() {
|
2018-11-02 19:13:33 -07:00
|
|
|
let expr = BudgetExpr::Pay(Payment {
|
2018-09-26 09:33:52 -07:00
|
|
|
tokens: 0,
|
2019-02-09 09:20:43 -08:00
|
|
|
to: Pubkey::default(),
|
2018-09-26 09:33:52 -07:00
|
|
|
});
|
2018-11-02 19:13:33 -07:00
|
|
|
let instruction = Instruction::NewBudget(expr);
|
2018-11-06 05:50:00 -08:00
|
|
|
let instructions = vec![transaction::Instruction::new(0, &instruction, vec![])];
|
2018-09-26 09:33:52 -07:00
|
|
|
let claim0 = Transaction {
|
2018-09-28 16:16:35 -07:00
|
|
|
account_keys: vec![],
|
2019-02-09 09:20:43 -08:00
|
|
|
last_id: Hash::default(),
|
2018-10-26 14:43:34 -07:00
|
|
|
signatures: vec![],
|
2018-10-04 13:36:15 -07:00
|
|
|
program_ids: vec![],
|
2018-09-28 16:16:35 -07:00
|
|
|
instructions,
|
2018-09-26 09:33:52 -07:00
|
|
|
fee: 0,
|
|
|
|
};
|
|
|
|
let buf = serialize(&claim0).unwrap();
|
|
|
|
let claim1: Transaction = deserialize(&buf).unwrap();
|
|
|
|
assert_eq!(claim1, claim0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_token_attack() {
|
|
|
|
let zero = Hash::default();
|
|
|
|
let keypair = Keypair::new();
|
|
|
|
let pubkey = keypair.pubkey();
|
2019-02-01 07:36:35 -08:00
|
|
|
let mut tx = BudgetTransaction::new(&keypair, pubkey, 42, zero);
|
|
|
|
let mut system_instruction = BudgetTransaction::system_instruction(&tx, 0).unwrap();
|
2018-11-16 08:04:46 -08:00
|
|
|
if let SystemInstruction::Move { ref mut tokens } = system_instruction {
|
2018-10-17 22:28:13 -07:00
|
|
|
*tokens = 1_000_000; // <-- attack, part 1!
|
2019-02-01 07:36:35 -08:00
|
|
|
let mut instruction = BudgetTransaction::instruction(&tx, 1).unwrap();
|
2018-11-02 19:13:33 -07:00
|
|
|
if let Instruction::NewBudget(ref mut expr) = instruction {
|
|
|
|
if let BudgetExpr::Pay(ref mut payment) = expr {
|
2018-10-17 22:28:13 -07:00
|
|
|
payment.tokens = *tokens; // <-- attack, part 2!
|
|
|
|
}
|
2018-09-26 09:33:52 -07:00
|
|
|
}
|
2018-10-17 22:28:13 -07:00
|
|
|
tx.instructions[1].userdata = serialize(&instruction).unwrap();
|
2018-09-26 09:33:52 -07:00
|
|
|
}
|
2018-10-17 22:28:13 -07:00
|
|
|
tx.instructions[0].userdata = serialize(&system_instruction).unwrap();
|
2019-02-01 07:36:35 -08:00
|
|
|
assert!(BudgetTransaction::verify_plan(&tx));
|
2018-09-26 09:33:52 -07:00
|
|
|
assert!(!tx.verify_signature());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_hijack_attack() {
|
|
|
|
let keypair0 = Keypair::new();
|
|
|
|
let keypair1 = Keypair::new();
|
|
|
|
let thief_keypair = Keypair::new();
|
|
|
|
let pubkey1 = keypair1.pubkey();
|
|
|
|
let zero = Hash::default();
|
2019-02-01 07:36:35 -08:00
|
|
|
let mut tx = BudgetTransaction::new(&keypair0, pubkey1, 42, zero);
|
|
|
|
let mut instruction = BudgetTransaction::instruction(&tx, 1);
|
2018-11-02 19:13:33 -07:00
|
|
|
if let Some(Instruction::NewBudget(ref mut expr)) = instruction {
|
|
|
|
if let BudgetExpr::Pay(ref mut payment) = expr {
|
2018-09-26 09:33:52 -07:00
|
|
|
payment.to = thief_keypair.pubkey(); // <-- attack!
|
|
|
|
}
|
|
|
|
}
|
2018-10-17 22:21:19 -07:00
|
|
|
tx.instructions[1].userdata = serialize(&instruction).unwrap();
|
2019-02-01 07:36:35 -08:00
|
|
|
assert!(BudgetTransaction::verify_plan(&tx));
|
2018-09-26 09:33:52 -07:00
|
|
|
assert!(!tx.verify_signature());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_overspend_attack() {
|
|
|
|
let keypair0 = Keypair::new();
|
|
|
|
let keypair1 = Keypair::new();
|
|
|
|
let zero = Hash::default();
|
2019-02-01 07:36:35 -08:00
|
|
|
let mut tx = BudgetTransaction::new(&keypair0, keypair1.pubkey(), 1, zero);
|
|
|
|
let mut instruction = BudgetTransaction::instruction(&tx, 1).unwrap();
|
2018-11-02 19:13:33 -07:00
|
|
|
if let Instruction::NewBudget(ref mut expr) = instruction {
|
|
|
|
if let BudgetExpr::Pay(ref mut payment) = expr {
|
2018-09-26 09:33:52 -07:00
|
|
|
payment.tokens = 2; // <-- attack!
|
|
|
|
}
|
|
|
|
}
|
2018-10-17 22:21:19 -07:00
|
|
|
tx.instructions[1].userdata = serialize(&instruction).unwrap();
|
2019-02-01 07:36:35 -08:00
|
|
|
assert!(!BudgetTransaction::verify_plan(&tx));
|
2018-09-26 09:33:52 -07:00
|
|
|
|
|
|
|
// Also, ensure all branchs of the plan spend all tokens
|
2019-02-01 07:36:35 -08:00
|
|
|
let mut instruction = BudgetTransaction::instruction(&tx, 1).unwrap();
|
2018-11-02 19:13:33 -07:00
|
|
|
if let Instruction::NewBudget(ref mut expr) = instruction {
|
|
|
|
if let BudgetExpr::Pay(ref mut payment) = expr {
|
2018-09-26 09:33:52 -07:00
|
|
|
payment.tokens = 0; // <-- whoops!
|
|
|
|
}
|
|
|
|
}
|
2018-10-17 22:21:19 -07:00
|
|
|
tx.instructions[1].userdata = serialize(&instruction).unwrap();
|
2019-02-01 07:36:35 -08:00
|
|
|
assert!(!BudgetTransaction::verify_plan(&tx));
|
2018-09-26 09:33:52 -07:00
|
|
|
}
|
|
|
|
}
|