Rename Budget to BudgetExpr
This commit is contained in:
parent
a264f8fa9b
commit
c8c255ad73
|
@ -1,4 +1,4 @@
|
|||
//! The `budget` module provides a domain-specific language for payment plans. Users create Budget objects that
|
||||
//! The `budget_expr` module provides a domain-specific language for payment plans. Users create BudgetExpr objects that
|
||||
//! are given to an interpreter. The interpreter listens for `Witness` transactions,
|
||||
//! which it uses to reduce the payment plan. When the budget is reduced to a
|
||||
//! `Payment`, the payment is executed.
|
||||
|
@ -34,7 +34,7 @@ impl Condition {
|
|||
/// A data type representing a payment plan.
|
||||
#[repr(C)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Budget {
|
||||
pub enum BudgetExpr {
|
||||
/// Make a payment.
|
||||
Pay(Payment),
|
||||
|
||||
|
@ -49,20 +49,20 @@ pub enum Budget {
|
|||
And(Condition, Condition, Payment),
|
||||
}
|
||||
|
||||
impl Budget {
|
||||
impl BudgetExpr {
|
||||
/// Create the simplest budget - one that pays `tokens` to Pubkey.
|
||||
pub fn new_payment(tokens: i64, to: Pubkey) -> Self {
|
||||
Budget::Pay(Payment { tokens, to })
|
||||
BudgetExpr::Pay(Payment { tokens, to })
|
||||
}
|
||||
|
||||
/// Create a budget that pays `tokens` to `to` after being witnessed by `from`.
|
||||
pub fn new_authorized_payment(from: Pubkey, tokens: i64, to: Pubkey) -> Self {
|
||||
Budget::After(Condition::Signature(from), Payment { tokens, to })
|
||||
BudgetExpr::After(Condition::Signature(from), Payment { tokens, to })
|
||||
}
|
||||
|
||||
/// Create a budget that pays tokens` to `to` after being witnessed by 2x `from`s
|
||||
pub fn new_2_2_multisig_payment(from0: Pubkey, from1: Pubkey, tokens: i64, to: Pubkey) -> Self {
|
||||
Budget::And(
|
||||
BudgetExpr::And(
|
||||
Condition::Signature(from0),
|
||||
Condition::Signature(from1),
|
||||
Payment { tokens, to },
|
||||
|
@ -71,7 +71,7 @@ impl Budget {
|
|||
|
||||
/// Create a budget that pays `tokens` to `to` after the given DateTime.
|
||||
pub fn new_future_payment(dt: DateTime<Utc>, from: Pubkey, tokens: i64, to: Pubkey) -> Self {
|
||||
Budget::After(Condition::Timestamp(dt, from), Payment { tokens, to })
|
||||
BudgetExpr::After(Condition::Timestamp(dt, from), Payment { tokens, to })
|
||||
}
|
||||
|
||||
/// Create a budget that pays `tokens` to `to` after the given DateTime
|
||||
|
@ -82,7 +82,7 @@ impl Budget {
|
|||
tokens: i64,
|
||||
to: Pubkey,
|
||||
) -> Self {
|
||||
Budget::Or(
|
||||
BudgetExpr::Or(
|
||||
(Condition::Timestamp(dt, from), Payment { tokens, to }),
|
||||
(Condition::Signature(from), Payment { tokens, to: from }),
|
||||
)
|
||||
|
@ -91,7 +91,7 @@ impl Budget {
|
|||
/// Return Payment if the budget requires no additional Witnesses.
|
||||
pub fn final_payment(&self) -> Option<Payment> {
|
||||
match self {
|
||||
Budget::Pay(payment) => Some(payment.clone()),
|
||||
BudgetExpr::Pay(payment) => Some(payment.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -99,39 +99,39 @@ impl Budget {
|
|||
/// Return true if the budget spends exactly `spendable_tokens`.
|
||||
pub fn verify(&self, spendable_tokens: i64) -> bool {
|
||||
match self {
|
||||
Budget::Pay(payment) | Budget::After(_, payment) | Budget::And(_, _, payment) => {
|
||||
BudgetExpr::Pay(payment) | BudgetExpr::After(_, payment) | BudgetExpr::And(_, _, payment) => {
|
||||
payment.tokens == spendable_tokens
|
||||
}
|
||||
Budget::Or(a, b) => a.1.tokens == spendable_tokens && b.1.tokens == spendable_tokens,
|
||||
BudgetExpr::Or(a, b) => a.1.tokens == spendable_tokens && b.1.tokens == spendable_tokens,
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a witness to the budget to see if the budget can be reduced.
|
||||
/// If so, modify the budget in-place.
|
||||
pub fn apply_witness(&mut self, witness: &Witness, from: &Pubkey) {
|
||||
let new_budget = match self {
|
||||
Budget::After(cond, payment) if cond.is_satisfied(witness, from) => {
|
||||
Some(Budget::Pay(payment.clone()))
|
||||
let new_expr = match self {
|
||||
BudgetExpr::After(cond, payment) if cond.is_satisfied(witness, from) => {
|
||||
Some(BudgetExpr::Pay(payment.clone()))
|
||||
}
|
||||
Budget::Or((cond, payment), _) if cond.is_satisfied(witness, from) => {
|
||||
Some(Budget::Pay(payment.clone()))
|
||||
BudgetExpr::Or((cond, payment), _) if cond.is_satisfied(witness, from) => {
|
||||
Some(BudgetExpr::Pay(payment.clone()))
|
||||
}
|
||||
Budget::Or(_, (cond, payment)) if cond.is_satisfied(witness, from) => {
|
||||
Some(Budget::Pay(payment.clone()))
|
||||
BudgetExpr::Or(_, (cond, payment)) if cond.is_satisfied(witness, from) => {
|
||||
Some(BudgetExpr::Pay(payment.clone()))
|
||||
}
|
||||
Budget::And(cond0, cond1, payment) => {
|
||||
BudgetExpr::And(cond0, cond1, payment) => {
|
||||
if cond0.is_satisfied(witness, from) {
|
||||
Some(Budget::After(cond1.clone(), payment.clone()))
|
||||
Some(BudgetExpr::After(cond1.clone(), payment.clone()))
|
||||
} else if cond1.is_satisfied(witness, from) {
|
||||
Some(Budget::After(cond0.clone(), payment.clone()))
|
||||
Some(BudgetExpr::After(cond0.clone(), payment.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(budget) = new_budget {
|
||||
mem::replace(self, budget);
|
||||
if let Some(expr) = new_expr {
|
||||
mem::replace(self, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,10 +162,10 @@ mod tests {
|
|||
let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
|
||||
let from = Pubkey::default();
|
||||
let to = Pubkey::default();
|
||||
assert!(Budget::new_payment(42, to).verify(42));
|
||||
assert!(Budget::new_authorized_payment(from, 42, to).verify(42));
|
||||
assert!(Budget::new_future_payment(dt, from, 42, to).verify(42));
|
||||
assert!(Budget::new_cancelable_future_payment(dt, from, 42, to).verify(42));
|
||||
assert!(BudgetExpr::new_payment(42, to).verify(42));
|
||||
assert!(BudgetExpr::new_authorized_payment(from, 42, to).verify(42));
|
||||
assert!(BudgetExpr::new_future_payment(dt, from, 42, to).verify(42));
|
||||
assert!(BudgetExpr::new_cancelable_future_payment(dt, from, 42, to).verify(42));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -173,9 +173,9 @@ mod tests {
|
|||
let from = Pubkey::default();
|
||||
let to = Pubkey::default();
|
||||
|
||||
let mut budget = Budget::new_authorized_payment(from, 42, to);
|
||||
budget.apply_witness(&Witness::Signature, &from);
|
||||
assert_eq!(budget, Budget::new_payment(42, to));
|
||||
let mut expr = BudgetExpr::new_authorized_payment(from, 42, to);
|
||||
expr.apply_witness(&Witness::Signature, &from);
|
||||
assert_eq!(expr, BudgetExpr::new_payment(42, to));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -184,9 +184,9 @@ mod tests {
|
|||
let from = Keypair::new().pubkey();
|
||||
let to = Keypair::new().pubkey();
|
||||
|
||||
let mut budget = Budget::new_future_payment(dt, from, 42, to);
|
||||
budget.apply_witness(&Witness::Timestamp(dt), &from);
|
||||
assert_eq!(budget, Budget::new_payment(42, to));
|
||||
let mut expr = BudgetExpr::new_future_payment(dt, from, 42, to);
|
||||
expr.apply_witness(&Witness::Timestamp(dt), &from);
|
||||
assert_eq!(expr, BudgetExpr::new_payment(42, to));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -197,10 +197,10 @@ mod tests {
|
|||
let from = Keypair::new().pubkey();
|
||||
let to = Keypair::new().pubkey();
|
||||
|
||||
let mut budget = Budget::new_future_payment(dt, from, 42, to);
|
||||
let orig_budget = budget.clone();
|
||||
budget.apply_witness(&Witness::Timestamp(dt), &to); // <-- Attack!
|
||||
assert_eq!(budget, orig_budget);
|
||||
let mut expr = BudgetExpr::new_future_payment(dt, from, 42, to);
|
||||
let orig_expr = expr.clone();
|
||||
expr.apply_witness(&Witness::Timestamp(dt), &to); // <-- Attack!
|
||||
assert_eq!(expr, orig_expr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -209,13 +209,13 @@ mod tests {
|
|||
let from = Pubkey::default();
|
||||
let to = Pubkey::default();
|
||||
|
||||
let mut budget = Budget::new_cancelable_future_payment(dt, from, 42, to);
|
||||
budget.apply_witness(&Witness::Timestamp(dt), &from);
|
||||
assert_eq!(budget, Budget::new_payment(42, to));
|
||||
let mut expr = BudgetExpr::new_cancelable_future_payment(dt, from, 42, to);
|
||||
expr.apply_witness(&Witness::Timestamp(dt), &from);
|
||||
assert_eq!(expr, BudgetExpr::new_payment(42, to));
|
||||
|
||||
let mut budget = Budget::new_cancelable_future_payment(dt, from, 42, to);
|
||||
budget.apply_witness(&Witness::Signature, &from);
|
||||
assert_eq!(budget, Budget::new_payment(42, from));
|
||||
let mut expr = BudgetExpr::new_cancelable_future_payment(dt, from, 42, to);
|
||||
expr.apply_witness(&Witness::Signature, &from);
|
||||
assert_eq!(expr, BudgetExpr::new_payment(42, from));
|
||||
}
|
||||
#[test]
|
||||
fn test_2_2_multisig_payment() {
|
||||
|
@ -223,8 +223,8 @@ mod tests {
|
|||
let from1 = Keypair::new().pubkey();
|
||||
let to = Pubkey::default();
|
||||
|
||||
let mut budget = Budget::new_2_2_multisig_payment(from0, from1, 42, to);
|
||||
budget.apply_witness(&Witness::Signature, &from0);
|
||||
assert_eq!(budget, Budget::new_authorized_payment(from1, 42, to));
|
||||
let mut expr = BudgetExpr::new_2_2_multisig_payment(from0, from1, 42, to);
|
||||
expr.apply_witness(&Witness::Signature, &from0);
|
||||
assert_eq!(expr, BudgetExpr::new_authorized_payment(from1, 42, to));
|
||||
}
|
||||
}
|
|
@ -1,19 +1,19 @@
|
|||
use budget::Budget;
|
||||
use budget_expr::BudgetExpr;
|
||||
use chrono::prelude::{DateTime, Utc};
|
||||
|
||||
/// A smart contract.
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Contract {
|
||||
/// The number of tokens allocated to the `Budget` and any transaction fees.
|
||||
/// The number of tokens allocated to the `BudgetExpr` and any transaction fees.
|
||||
pub tokens: i64,
|
||||
pub budget: Budget,
|
||||
pub budget_expr: BudgetExpr,
|
||||
}
|
||||
|
||||
/// An instruction to progress the smart contract.
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Instruction {
|
||||
/// Declare and instantiate `Budget`.
|
||||
NewBudget(Budget),
|
||||
/// Declare and instantiate `BudgetExpr`.
|
||||
NewBudget(BudgetExpr),
|
||||
|
||||
/// Tell a payment plan acknowledge the given `DateTime` has past.
|
||||
ApplyTimestamp(DateTime<Utc>),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! budget program
|
||||
use bincode::{self, deserialize, serialize_into, serialized_size};
|
||||
use budget::Budget;
|
||||
use budget_expr::BudgetExpr;
|
||||
use budget_instruction::Instruction;
|
||||
use chrono::prelude::{DateTime, Utc};
|
||||
use payment_plan::Witness;
|
||||
|
@ -27,7 +27,7 @@ pub enum BudgetError {
|
|||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
|
||||
pub struct BudgetState {
|
||||
pub initialized: bool,
|
||||
pub pending_budget: Option<Budget>,
|
||||
pub pending_budget: Option<BudgetExpr>,
|
||||
}
|
||||
|
||||
const BUDGET_PROGRAM_ID: [u8; 32] = [
|
||||
|
@ -55,13 +55,13 @@ impl BudgetState {
|
|||
accounts: &mut [&mut Account],
|
||||
) -> Result<(), BudgetError> {
|
||||
let mut final_payment = None;
|
||||
if let Some(ref mut budget) = self.pending_budget {
|
||||
if let Some(ref mut expr) = self.pending_budget {
|
||||
let key = match tx.signed_key(instruction_index, 0) {
|
||||
None => return Err(BudgetError::UnsignedKey),
|
||||
Some(key) => key,
|
||||
};
|
||||
budget.apply_witness(&Witness::Signature, key);
|
||||
final_payment = budget.final_payment();
|
||||
expr.apply_witness(&Witness::Signature, key);
|
||||
final_payment = expr.final_payment();
|
||||
}
|
||||
|
||||
if let Some(payment) = final_payment {
|
||||
|
@ -88,13 +88,13 @@ impl BudgetState {
|
|||
// Check to see if any timelocked transactions can be completed.
|
||||
let mut final_payment = None;
|
||||
|
||||
if let Some(ref mut budget) = self.pending_budget {
|
||||
if let Some(ref mut expr) = self.pending_budget {
|
||||
let key = match tx.signed_key(instruction_index, 0) {
|
||||
None => return Err(BudgetError::UnsignedKey),
|
||||
Some(key) => key,
|
||||
};
|
||||
budget.apply_witness(&Witness::Timestamp(dt), key);
|
||||
final_payment = budget.final_payment();
|
||||
expr.apply_witness(&Witness::Timestamp(dt), key);
|
||||
final_payment = expr.final_payment();
|
||||
}
|
||||
|
||||
if let Some(payment) = final_payment {
|
||||
|
@ -120,9 +120,9 @@ impl BudgetState {
|
|||
return Err(BudgetError::SourceIsPendingContract);
|
||||
}
|
||||
match instruction {
|
||||
Instruction::NewBudget(budget) => {
|
||||
let budget = budget.clone();
|
||||
if let Some(payment) = budget.final_payment() {
|
||||
Instruction::NewBudget(expr) => {
|
||||
let expr = expr.clone();
|
||||
if let Some(payment) = expr.final_payment() {
|
||||
accounts[1].tokens += payment.tokens;
|
||||
Ok(())
|
||||
} else {
|
||||
|
@ -132,7 +132,7 @@ impl BudgetState {
|
|||
Err(BudgetError::ContractAlreadyExists)
|
||||
} else {
|
||||
let mut state = BudgetState::default();
|
||||
state.pending_budget = Some(budget);
|
||||
state.pending_budget = Some(expr);
|
||||
accounts[1].tokens += accounts[0].tokens;
|
||||
accounts[0].tokens = 0;
|
||||
state.initialized = true;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! The `budget_transaction` module provides functionality for creating Budget transactions.
|
||||
|
||||
use bincode::{deserialize, serialize};
|
||||
use budget::{Budget, Condition};
|
||||
use budget_expr::{BudgetExpr, Condition};
|
||||
use budget_instruction::Instruction;
|
||||
use budget_program::BudgetState;
|
||||
use chrono::prelude::*;
|
||||
|
@ -84,7 +84,7 @@ impl BudgetTransaction for Transaction {
|
|||
tokens: tokens - fee,
|
||||
to,
|
||||
};
|
||||
let budget_instruction = Instruction::NewBudget(Budget::Pay(payment));
|
||||
let budget_instruction = Instruction::NewBudget(BudgetExpr::Pay(payment));
|
||||
let pay_userdata = serialize(&budget_instruction).unwrap();
|
||||
|
||||
let program_ids = vec![SystemProgram::id(), BudgetState::id()];
|
||||
|
@ -160,15 +160,15 @@ impl BudgetTransaction for Transaction {
|
|||
tokens: i64,
|
||||
last_id: Hash,
|
||||
) -> Self {
|
||||
let budget = if let Some(from) = cancelable {
|
||||
Budget::Or(
|
||||
let expr = if let Some(from) = cancelable {
|
||||
BudgetExpr::Or(
|
||||
(Condition::Timestamp(dt, dt_pubkey), Payment { tokens, to }),
|
||||
(Condition::Signature(from), Payment { tokens, to: from }),
|
||||
)
|
||||
} else {
|
||||
Budget::After(Condition::Timestamp(dt, dt_pubkey), Payment { tokens, to })
|
||||
BudgetExpr::After(Condition::Timestamp(dt, dt_pubkey), Payment { tokens, to })
|
||||
};
|
||||
let instruction = Instruction::NewBudget(budget);
|
||||
let instruction = Instruction::NewBudget(expr);
|
||||
let userdata = serialize(&instruction).expect("serialize instruction");
|
||||
Self::new(
|
||||
from_keypair,
|
||||
|
@ -189,15 +189,15 @@ impl BudgetTransaction for Transaction {
|
|||
tokens: i64,
|
||||
last_id: Hash,
|
||||
) -> Self {
|
||||
let budget = if let Some(from) = cancelable {
|
||||
Budget::Or(
|
||||
let expr = if let Some(from) = cancelable {
|
||||
BudgetExpr::Or(
|
||||
(Condition::Signature(witness), Payment { tokens, to }),
|
||||
(Condition::Signature(from), Payment { tokens, to: from }),
|
||||
)
|
||||
} else {
|
||||
Budget::After(Condition::Signature(witness), Payment { tokens, to })
|
||||
BudgetExpr::After(Condition::Signature(witness), Payment { tokens, to })
|
||||
};
|
||||
let instruction = Instruction::NewBudget(budget);
|
||||
let instruction = Instruction::NewBudget(expr);
|
||||
let userdata = serialize(&instruction).expect("serialize instruction");
|
||||
Self::new(
|
||||
from_keypair,
|
||||
|
@ -220,8 +220,8 @@ impl BudgetTransaction for Transaction {
|
|||
/// Verify only the payment plan.
|
||||
fn verify_plan(&self) -> bool {
|
||||
if let Some(SystemProgram::Move { tokens }) = self.system_instruction(0) {
|
||||
if let Some(Instruction::NewBudget(budget)) = self.instruction(1) {
|
||||
if !(self.fee >= 0 && self.fee <= tokens && budget.verify(tokens - self.fee)) {
|
||||
if let Some(Instruction::NewBudget(expr)) = self.instruction(1) {
|
||||
if !(self.fee >= 0 && self.fee <= tokens && expr.verify(tokens - self.fee)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -267,11 +267,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_serialize_claim() {
|
||||
let budget = Budget::Pay(Payment {
|
||||
let expr = BudgetExpr::Pay(Payment {
|
||||
tokens: 0,
|
||||
to: Default::default(),
|
||||
});
|
||||
let instruction = Instruction::NewBudget(budget);
|
||||
let instruction = Instruction::NewBudget(expr);
|
||||
let userdata = serialize(&instruction).unwrap();
|
||||
let instructions = vec![transaction::Instruction {
|
||||
program_ids_index: 0,
|
||||
|
@ -301,8 +301,8 @@ mod tests {
|
|||
if let SystemProgram::Move { ref mut tokens } = system_instruction {
|
||||
*tokens = 1_000_000; // <-- attack, part 1!
|
||||
let mut instruction = tx.instruction(1).unwrap();
|
||||
if let Instruction::NewBudget(ref mut budget) = instruction {
|
||||
if let Budget::Pay(ref mut payment) = budget {
|
||||
if let Instruction::NewBudget(ref mut expr) = instruction {
|
||||
if let BudgetExpr::Pay(ref mut payment) = expr {
|
||||
payment.tokens = *tokens; // <-- attack, part 2!
|
||||
}
|
||||
}
|
||||
|
@ -322,8 +322,8 @@ mod tests {
|
|||
let zero = Hash::default();
|
||||
let mut tx = Transaction::budget_new(&keypair0, pubkey1, 42, zero);
|
||||
let mut instruction = tx.instruction(1);
|
||||
if let Some(Instruction::NewBudget(ref mut budget)) = instruction {
|
||||
if let Budget::Pay(ref mut payment) = budget {
|
||||
if let Some(Instruction::NewBudget(ref mut expr)) = instruction {
|
||||
if let BudgetExpr::Pay(ref mut payment) = expr {
|
||||
payment.to = thief_keypair.pubkey(); // <-- attack!
|
||||
}
|
||||
}
|
||||
|
@ -339,8 +339,8 @@ mod tests {
|
|||
let zero = Hash::default();
|
||||
let mut tx = Transaction::budget_new(&keypair0, keypair1.pubkey(), 1, zero);
|
||||
let mut instruction = tx.instruction(1).unwrap();
|
||||
if let Instruction::NewBudget(ref mut budget) = instruction {
|
||||
if let Budget::Pay(ref mut payment) = budget {
|
||||
if let Instruction::NewBudget(ref mut expr) = instruction {
|
||||
if let BudgetExpr::Pay(ref mut payment) = expr {
|
||||
payment.tokens = 2; // <-- attack!
|
||||
}
|
||||
}
|
||||
|
@ -349,8 +349,8 @@ mod tests {
|
|||
|
||||
// Also, ensure all branchs of the plan spend all tokens
|
||||
let mut instruction = tx.instruction(1).unwrap();
|
||||
if let Instruction::NewBudget(ref mut budget) = instruction {
|
||||
if let Budget::Pay(ref mut payment) = budget {
|
||||
if let Instruction::NewBudget(ref mut expr) = instruction {
|
||||
if let BudgetExpr::Pay(ref mut payment) = expr {
|
||||
payment.tokens = 0; // <-- whoops!
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ pub mod banking_stage;
|
|||
pub mod blob_fetch_stage;
|
||||
pub mod bpf_loader;
|
||||
pub mod broadcast_stage;
|
||||
pub mod budget;
|
||||
pub mod budget_expr;
|
||||
pub mod budget_instruction;
|
||||
pub mod budget_transaction;
|
||||
#[cfg(feature = "chacha")]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! The `plan` module provides a domain-specific language for payment plans. Users create Budget objects that
|
||||
//! The `plan` module provides a domain-specific language for payment plans. Users create BudgetExpr objects that
|
||||
//! are given to an interpreter. The interpreter listens for `Witness` transactions,
|
||||
//! which it uses to reduce the payment plan. When the plan is reduced to a
|
||||
//! `Payment`, the payment is executed.
|
||||
|
|
Loading…
Reference in New Issue