First go at smart contracts

Needs lots of cleanup.
This commit is contained in:
Greg Fitzgerald
2018-03-10 22:00:27 -07:00
parent 8579795c40
commit f5f71a19b8
3 changed files with 76 additions and 99 deletions

View File

@ -83,8 +83,12 @@ impl Accountant {
self.last_id self.last_id
} }
fn is_deposit(allow_deposits: bool, from: &PublicKey, to: &PublicKey) -> bool { fn is_deposit(allow_deposits: bool, from: &PublicKey, plan: &Plan<i64>) -> bool {
allow_deposits && from == to if let Plan::Action(Action::Pay(ref payment)) = *plan {
allow_deposits && *from == payment.to
} else {
false
}
} }
pub fn process_transaction(self: &mut Self, tr: Transaction<i64>) -> Result<()> { pub fn process_transaction(self: &mut Self, tr: Transaction<i64>) -> Result<()> {
@ -109,38 +113,49 @@ impl Accountant {
/// Commit funds to the 'to' party. /// Commit funds to the 'to' party.
fn complete_transaction(self: &mut Self, plan: &Plan<i64>) { fn complete_transaction(self: &mut Self, plan: &Plan<i64>) {
let Action::Pay(ref payment) = plan.if_all.1; let payment = match *plan {
let to = payment.to; Plan::Action(Action::Pay(ref payment)) => Some(payment),
if self.balances.contains_key(&to) { Plan::After(_, Action::Pay(ref payment)) => Some(payment),
if let Some(x) = self.balances.get_mut(&to) { Plan::Race(ref plan_a, _) => {
if let Plan::After(_, Action::Pay(ref payment)) = **plan_a {
Some(payment)
} else {
None
}
}
};
if let Some(payment) = payment {
if self.balances.contains_key(&payment.to) {
if let Some(x) = self.balances.get_mut(&payment.to) {
*x += payment.asset; *x += payment.asset;
} }
} else { } else {
self.balances.insert(to, payment.asset); self.balances.insert(payment.to, payment.asset);
}
} }
} }
/// Return funds to the 'from' party. /// Return funds to the 'from' party.
fn cancel_transaction(self: &mut Self, plan: &Plan<i64>) { fn cancel_transaction(self: &mut Self, plan: &Plan<i64>) {
let Action::Pay(ref payment) = plan.unless_any.1; if let Plan::Race(_, ref cancel_plan) = *plan {
if let Plan::After(_, Action::Pay(ref payment)) = **cancel_plan {
if let Some(x) = self.balances.get_mut(&payment.to) { if let Some(x) = self.balances.get_mut(&payment.to) {
*x += payment.asset; *x += payment.asset;
} }
} }
}
}
// TODO: Move this to transaction.rs // TODO: Move this to transaction.rs
fn all_satisfied(&self, conds: &[Condition]) -> bool { fn all_satisfied(&self, plan: &Plan<i64>) -> bool {
let mut satisfied = true; match *plan {
for cond in conds { Plan::Action(_) => true,
if let &Condition::Timestamp(dt) = cond { Plan::After(Condition::Timestamp(dt), _) => dt <= self.last_time,
if dt > self.last_time { Plan::After(Condition::Signature(_), _) => false,
satisfied = false; Plan::Race(ref plan_a, ref plan_b) => {
} self.all_satisfied(plan_a) || self.all_satisfied(plan_b)
} else {
satisfied = false;
} }
} }
satisfied
} }
fn process_verified_transaction( fn process_verified_transaction(
@ -152,17 +167,13 @@ impl Accountant {
return Err(AccountingError::InvalidTransferSignature); return Err(AccountingError::InvalidTransferSignature);
} }
if !tr.plan.unless_any.0.is_empty() { if !Self::is_deposit(allow_deposits, &tr.from, &tr.plan) {
// TODO: Check to see if the transaction is expired.
}
if !Self::is_deposit(allow_deposits, &tr.from, &tr.plan.to()) {
if let Some(x) = self.balances.get_mut(&tr.from) { if let Some(x) = self.balances.get_mut(&tr.from) {
*x -= tr.asset; *x -= tr.asset;
} }
} }
if !self.all_satisfied(&tr.plan.if_all.0) { if !self.all_satisfied(&tr.plan) {
self.pending.insert(tr.sig, tr.plan.clone()); self.pending.insert(tr.sig, tr.plan.clone());
return Ok(()); return Ok(());
} }
@ -178,11 +189,10 @@ impl Accountant {
// if Signature(from) is in unless_any, return funds to tx.from, and remove the tx from this map. // if Signature(from) is in unless_any, return funds to tx.from, and remove the tx from this map.
// TODO: Use find(). // TODO: Use find().
for cond in &plan.unless_any.0 { if let Plan::Race(_, ref plan_b) = *plan {
if let Condition::Signature(pubkey) = *cond { if let Plan::After(Condition::Signature(pubkey), _) = **plan_b {
if from == pubkey { if from == pubkey {
cancel = true; cancel = true;
break;
} }
} }
} }
@ -223,12 +233,14 @@ impl Accountant {
// Check to see if any timelocked transactions can be completed. // Check to see if any timelocked transactions can be completed.
let mut completed = vec![]; let mut completed = vec![];
for (key, plan) in &self.pending { for (key, plan) in &self.pending {
for cond in &plan.if_all.0 { if let Plan::After(Condition::Timestamp(dt), _) = *plan {
if let Condition::Timestamp(dt) = *cond {
if self.last_time >= dt { if self.last_time >= dt {
if plan.if_all.0.len() == 1 {
completed.push(*key); completed.push(*key);
} }
} else if let Plan::Race(ref plan_a, _) = *plan {
if let Plan::After(Condition::Timestamp(dt), _) = **plan_a {
if self.last_time >= dt {
completed.push(*key);
} }
} }
} }

View File

@ -58,12 +58,15 @@ impl Mint {
mod tests { mod tests {
use super::*; use super::*;
use log::verify_slice; use log::verify_slice;
use transaction::{Action, Plan};
#[test] #[test]
fn test_create_events() { fn test_create_events() {
let mut events = Mint::new(100).create_events().into_iter(); let mut events = Mint::new(100).create_events().into_iter();
if let Event::Transaction(tr) = events.next().unwrap() { if let Event::Transaction(tr) = events.next().unwrap() {
assert_eq!(tr.from, tr.plan.to()); if let Plan::Action(Action::Pay(payment)) = tr.plan {
assert_eq!(tr.from, payment.to);
}
} }
assert_eq!(events.next(), None); assert_eq!(events.next(), None);
} }

View File

@ -24,16 +24,10 @@ pub struct Payment<T> {
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Plan<T> { pub enum Plan<T> {
pub if_all: (Vec<Condition>, Action<T>), Action(Action<T>),
pub unless_any: (Vec<Condition>, Action<T>), After(Condition, Action<T>),
} Race(Box<Plan<T>>, Box<Plan<T>>),
impl<T> Plan<T> {
pub fn to(&self) -> PublicKey {
let Action::Pay(ref payment) = self.if_all.1;
payment.to
}
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
@ -48,22 +42,10 @@ pub struct Transaction<T> {
impl<T: Serialize + Clone> Transaction<T> { impl<T: Serialize + Clone> Transaction<T> {
pub fn new(from_keypair: &KeyPair, to: PublicKey, asset: T, last_id: Hash) -> Self { pub fn new(from_keypair: &KeyPair, to: PublicKey, asset: T, last_id: Hash) -> Self {
let from = from_keypair.pubkey(); let from = from_keypair.pubkey();
let plan = Plan { let plan = Plan::Action(Action::Pay(Payment {
if_all: (
vec![],
Action::Pay(Payment {
asset: asset.clone(), asset: asset.clone(),
to, to,
}), }));
),
unless_any: (
vec![Condition::Signature(from)],
Action::Pay(Payment {
asset: asset.clone(),
to: from,
}),
),
};
let mut tr = Transaction { let mut tr = Transaction {
from, from,
plan, plan,
@ -83,22 +65,22 @@ impl<T: Serialize + Clone> Transaction<T> {
last_id: Hash, last_id: Hash,
) -> Self { ) -> Self {
let from = from_keypair.pubkey(); let from = from_keypair.pubkey();
let plan = Plan { let plan = Plan::Race(
if_all: ( Box::new(Plan::After(
vec![Condition::Timestamp(dt)], Condition::Timestamp(dt),
Action::Pay(Payment { Action::Pay(Payment {
asset: asset.clone(), asset: asset.clone(),
to, to,
}), }),
), )),
unless_any: ( Box::new(Plan::After(
vec![Condition::Signature(from)], Condition::Signature(from),
Action::Pay(Payment { Action::Pay(Payment {
asset: asset.clone(), asset: asset.clone(),
to: from, to: from,
}), }),
), )),
}; );
let mut tr = Transaction { let mut tr = Transaction {
from, from,
plan, plan,
@ -111,14 +93,7 @@ impl<T: Serialize + Clone> Transaction<T> {
} }
fn get_sign_data(&self) -> Vec<u8> { fn get_sign_data(&self) -> Vec<u8> {
let plan = &self.plan; serialize(&(&self.from, &self.plan, &self.asset, &self.last_id)).unwrap()
serialize(&(
&self.from,
&plan.if_all,
&plan.unless_any,
&self.asset,
&self.last_id,
)).unwrap()
} }
pub fn sign(&mut self, keypair: &KeyPair) { pub fn sign(&mut self, keypair: &KeyPair) {
@ -159,22 +134,10 @@ mod tests {
#[test] #[test]
fn test_serialize_claim() { fn test_serialize_claim() {
let plan = Plan { let plan = Plan::Action(Action::Pay(Payment {
if_all: (
Default::default(),
Action::Pay(Payment {
asset: 0, asset: 0,
to: Default::default(), to: Default::default(),
}), }));
),
unless_any: (
Default::default(),
Action::Pay(Payment {
asset: 0,
to: Default::default(),
}),
),
};
let claim0 = Transaction { let claim0 = Transaction {
from: Default::default(), from: Default::default(),
plan, plan,
@ -208,10 +171,9 @@ mod tests {
let asset = hash(b"hello, world"); let asset = hash(b"hello, world");
let mut tr = Transaction::new(&keypair0, pubkey1, asset, zero); let mut tr = Transaction::new(&keypair0, pubkey1, asset, zero);
tr.sign(&keypair0); tr.sign(&keypair0);
tr.plan.if_all.1 = Action::Pay(Payment { if let Plan::Action(Action::Pay(ref mut payment)) = tr.plan {
asset, payment.to = thief_keypair.pubkey(); // <-- attack!
to: thief_keypair.pubkey(), };
}); // <-- attack!
assert!(!tr.verify()); assert!(!tr.verify());
} }
} }