Boot Contract type from Budget

In the old bank (before the contract engine), Contract wasn't specific
to Budget. It provided the same service as what is now called
SystemProgram::Move, but without requiring a separate account.
This commit is contained in:
Greg Fitzgerald 2018-10-12 23:50:43 -06:00
parent d0f43e9934
commit 15a89d4f17
3 changed files with 27 additions and 37 deletions

View File

@ -1,13 +1,6 @@
use budget::Budget; use budget::Budget;
use chrono::prelude::{DateTime, Utc}; 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.
pub tokens: i64,
pub budget: Budget,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Vote { pub struct Vote {
/// We send some gossip specific membership information through the vote to shortcut /// We send some gossip specific membership information through the vote to shortcut
@ -22,13 +15,13 @@ pub struct Vote {
/// An instruction to progress the smart contract. /// An instruction to progress the smart contract.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Instruction { pub enum Instruction {
/// Declare and instantiate `Contract`. /// Declare and instantiate `Budget`.
NewContract(Contract), NewBudget(i64, Budget),
/// Tell a payment plan acknowledge the given `DateTime` has past. /// Tell a payment plan acknowledge the given `DateTime` has past.
ApplyTimestamp(DateTime<Utc>), ApplyTimestamp(DateTime<Utc>),
/// Tell the payment plan that the `NewContract` with `Signature` has been /// Tell the budget that the `NewBudget` with `Signature` has been
/// signed by the containing transaction's `Pubkey`. /// signed by the containing transaction's `Pubkey`.
ApplySignature, ApplySignature,

View File

@ -112,17 +112,17 @@ impl BudgetState {
trace!("source is pending"); trace!("source is pending");
return Err(BudgetError::SourceIsPendingContract); return Err(BudgetError::SourceIsPendingContract);
} }
if let Instruction::NewContract(contract) = &instruction { if let Instruction::NewBudget(tokens, _) = instruction {
if contract.tokens < 0 { if *tokens < 0 {
trace!("negative tokens"); trace!("negative tokens");
return Err(BudgetError::NegativeTokens); return Err(BudgetError::NegativeTokens);
} }
if accounts[0].tokens < contract.tokens { if accounts[0].tokens < *tokens {
trace!("insufficient funds"); trace!("insufficient funds");
return Err(BudgetError::InsufficientFunds); return Err(BudgetError::InsufficientFunds);
} else { } else {
accounts[0].tokens -= contract.tokens; accounts[0].tokens -= *tokens;
} }
}; };
} }
@ -138,8 +138,8 @@ impl BudgetState {
instruction: &Instruction, instruction: &Instruction,
) -> Result<(), BudgetError> { ) -> Result<(), BudgetError> {
match instruction { match instruction {
Instruction::NewContract(contract) => { Instruction::NewBudget(tokens, budget) => {
let budget = contract.budget.clone(); let budget = budget.clone();
if let Some(payment) = budget.final_payment() { if let Some(payment) = budget.final_payment() {
accounts[1].tokens += payment.tokens; accounts[1].tokens += payment.tokens;
Ok(()) Ok(())
@ -151,7 +151,7 @@ impl BudgetState {
} else { } else {
let mut state = BudgetState::default(); let mut state = BudgetState::default();
state.pending_budget = Some(budget); state.pending_budget = Some(budget);
accounts[1].tokens += contract.tokens; accounts[1].tokens += tokens;
state.initialized = true; state.initialized = true;
state.serialize(&mut accounts[1].userdata) state.serialize(&mut accounts[1].userdata)
} }

View File

@ -2,7 +2,7 @@
use bincode::{deserialize, serialize}; use bincode::{deserialize, serialize};
use budget::{Budget, Condition}; use budget::{Budget, Condition};
use budget_instruction::{Contract, Instruction, Vote}; use budget_instruction::{Instruction, Vote};
use budget_program::BudgetState; use budget_program::BudgetState;
use chrono::prelude::*; use chrono::prelude::*;
use hash::Hash; use hash::Hash;
@ -81,7 +81,7 @@ impl BudgetTransaction for Transaction {
to, to,
}; };
let budget = Budget::Pay(payment); let budget = Budget::Pay(payment);
let instruction = Instruction::NewContract(Contract { budget, tokens }); let instruction = Instruction::NewBudget(tokens, budget);
let userdata = serialize(&instruction).unwrap(); let userdata = serialize(&instruction).unwrap();
Self::new( Self::new(
from_keypair, from_keypair,
@ -162,7 +162,7 @@ impl BudgetTransaction for Transaction {
} else { } else {
Budget::After(Condition::Timestamp(dt, dt_pubkey), Payment { tokens, to }) Budget::After(Condition::Timestamp(dt, dt_pubkey), Payment { tokens, to })
}; };
let instruction = Instruction::NewContract(Contract { budget, tokens }); let instruction = Instruction::NewBudget(tokens, budget);
let userdata = serialize(&instruction).expect("serialize instruction"); let userdata = serialize(&instruction).expect("serialize instruction");
Self::new( Self::new(
from_keypair, from_keypair,
@ -191,7 +191,7 @@ impl BudgetTransaction for Transaction {
} else { } else {
Budget::After(Condition::Signature(witness), Payment { tokens, to }) Budget::After(Condition::Signature(witness), Payment { tokens, to })
}; };
let instruction = Instruction::NewContract(Contract { budget, tokens }); let instruction = Instruction::NewBudget(tokens, budget);
let userdata = serialize(&instruction).expect("serialize instruction"); let userdata = serialize(&instruction).expect("serialize instruction");
Self::new( Self::new(
from_keypair, from_keypair,
@ -220,11 +220,8 @@ impl BudgetTransaction for Transaction {
/// Verify only the payment plan. /// Verify only the payment plan.
fn verify_plan(&self) -> bool { fn verify_plan(&self) -> bool {
for pix in 0..self.instructions.len() { for pix in 0..self.instructions.len() {
if let Some(Instruction::NewContract(contract)) = self.instruction(pix) { if let Some(Instruction::NewBudget(tokens, budget)) = self.instruction(pix) {
if !(self.fee >= 0 if !(self.fee >= 0 && self.fee <= tokens && budget.verify(tokens - self.fee)) {
&& self.fee <= contract.tokens
&& contract.budget.verify(contract.tokens - self.fee))
{
return false; return false;
} }
} }
@ -274,7 +271,7 @@ mod tests {
tokens: 0, tokens: 0,
to: Default::default(), to: Default::default(),
}); });
let instruction = Instruction::NewContract(Contract { budget, tokens: 0 }); let instruction = Instruction::NewBudget(0, budget);
let userdata = serialize(&instruction).unwrap(); let userdata = serialize(&instruction).unwrap();
let instructions = vec![transaction::Instruction { let instructions = vec![transaction::Instruction {
program_ids_index: 0, program_ids_index: 0,
@ -301,10 +298,10 @@ mod tests {
let pubkey = keypair.pubkey(); let pubkey = keypair.pubkey();
let mut tx = Transaction::budget_new(&keypair, pubkey, 42, zero); let mut tx = Transaction::budget_new(&keypair, pubkey, 42, zero);
let mut instruction = tx.instruction(0).unwrap(); let mut instruction = tx.instruction(0).unwrap();
if let Instruction::NewContract(ref mut contract) = instruction { if let Instruction::NewBudget(ref mut tokens, ref mut budget) = instruction {
contract.tokens = 1_000_000; // <-- attack, part 1! *tokens = 1_000_000; // <-- attack, part 1!
if let Budget::Pay(ref mut payment) = contract.budget { if let Budget::Pay(ref mut payment) = budget {
payment.tokens = contract.tokens; // <-- attack, part 2! payment.tokens = *tokens; // <-- attack, part 2!
} }
} }
tx.instructions[0].userdata = serialize(&instruction).unwrap(); tx.instructions[0].userdata = serialize(&instruction).unwrap();
@ -321,8 +318,8 @@ mod tests {
let zero = Hash::default(); let zero = Hash::default();
let mut tx = Transaction::budget_new(&keypair0, pubkey1, 42, zero); let mut tx = Transaction::budget_new(&keypair0, pubkey1, 42, zero);
let mut instruction = tx.instruction(0); let mut instruction = tx.instruction(0);
if let Some(Instruction::NewContract(ref mut contract)) = instruction { if let Some(Instruction::NewBudget(_, ref mut budget)) = instruction {
if let Budget::Pay(ref mut payment) = contract.budget { if let Budget::Pay(ref mut payment) = budget {
payment.to = thief_keypair.pubkey(); // <-- attack! payment.to = thief_keypair.pubkey(); // <-- attack!
} }
} }
@ -338,8 +335,8 @@ mod tests {
let zero = Hash::default(); let zero = Hash::default();
let mut tx = Transaction::budget_new(&keypair0, keypair1.pubkey(), 1, zero); let mut tx = Transaction::budget_new(&keypair0, keypair1.pubkey(), 1, zero);
let mut instruction = tx.instruction(0).unwrap(); let mut instruction = tx.instruction(0).unwrap();
if let Instruction::NewContract(ref mut contract) = instruction { if let Instruction::NewBudget(_, ref mut budget) = instruction {
if let Budget::Pay(ref mut payment) = contract.budget { if let Budget::Pay(ref mut payment) = budget {
payment.tokens = 2; // <-- attack! payment.tokens = 2; // <-- attack!
} }
} }
@ -348,8 +345,8 @@ mod tests {
// Also, ensure all branchs of the plan spend all tokens // Also, ensure all branchs of the plan spend all tokens
let mut instruction = tx.instruction(0).unwrap(); let mut instruction = tx.instruction(0).unwrap();
if let Instruction::NewContract(ref mut contract) = instruction { if let Instruction::NewBudget(_, ref mut budget) = instruction {
if let Budget::Pay(ref mut payment) = contract.budget { if let Budget::Pay(ref mut payment) = budget {
payment.tokens = 0; // <-- whoops! payment.tokens = 0; // <-- whoops!
} }
} }