diff --git a/Cargo.lock b/Cargo.lock index 4bda693c1f..11c20e3d33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4306,22 +4306,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "solana-budget-program" -version = "1.8.0" -dependencies = [ - "bincode", - "chrono", - "log 0.4.11", - "num-derive", - "num-traits", - "serde", - "serde_derive", - "solana-runtime", - "solana-sdk", - "thiserror", -] - [[package]] name = "solana-cargo-build-bpf" version = "1.8.0" @@ -4740,7 +4724,6 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "solana-budget-program", "solana-clap-utils", "solana-cli-config", "solana-exchange-program", @@ -4888,7 +4871,6 @@ dependencies = [ "sha2 0.9.5", "solana-account-decoder", "solana-bpf-loader-program", - "solana-budget-program", "solana-frozen-abi 1.8.0", "solana-frozen-abi-macro 1.8.0", "solana-logger 1.8.0", @@ -5132,11 +5114,11 @@ dependencies = [ "rand 0.7.3", "rayon", "serde", - "solana-budget-program", "solana-logger 1.8.0", "solana-metrics", "solana-rayon-threadlimit", "solana-sdk", + "solana-stake-program", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 30b2bdeb9f..675493dea2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,6 @@ members = [ "program-test", "programs/secp256k1", "programs/bpf_loader", - "programs/budget", "programs/config", "programs/exchange", "programs/failure", diff --git a/genesis/Cargo.toml b/genesis/Cargo.toml index 8668efdb86..69fa005f02 100644 --- a/genesis/Cargo.toml +++ b/genesis/Cargo.toml @@ -16,7 +16,6 @@ chrono = "0.4" serde = "1.0.126" serde_json = "1.0.64" serde_yaml = "0.8.13" -solana-budget-program = { path = "../programs/budget", version = "=1.8.0" } solana-clap-utils = { path = "../clap-utils", version = "=1.8.0" } solana-cli-config = { path = "../cli-config", version = "=1.8.0" } solana-exchange-program = { path = "../programs/exchange", version = "=1.8.0" } diff --git a/genesis/src/main.rs b/genesis/src/main.rs index d86d38668e..63a83930e9 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -1,8 +1,6 @@ //! A command-line executable for generating the chain's genesis config. #![allow(clippy::integer_arithmetic)] -#[macro_use] -extern crate solana_budget_program; #[macro_use] extern crate solana_exchange_program; @@ -492,7 +490,7 @@ fn main() -> Result<(), Box> { ); let native_instruction_processors = if cluster_type == ClusterType::Development { - vec![solana_budget_program!(), solana_exchange_program!()] + vec![solana_exchange_program!()] } else { vec![] }; diff --git a/ledger/Cargo.toml b/ledger/Cargo.toml index 12df3a93e1..5d07d50b29 100644 --- a/ledger/Cargo.toml +++ b/ledger/Cargo.toml @@ -67,7 +67,7 @@ features = ["lz4"] assert_matches = "1.3.0" matches = "0.1.6" solana-account-decoder = { path = "../account-decoder", version = "=1.8.0" } -solana-budget-program = { path = "../programs/budget", version = "=1.8.0" } +solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } [build-dependencies] rustc_version = "0.2" diff --git a/ledger/src/entry.rs b/ledger/src/entry.rs index f918bb37bc..8e7ea398ae 100644 --- a/ledger/src/entry.rs +++ b/ledger/src/entry.rs @@ -724,40 +724,16 @@ pub fn next_entry(prev_hash: &Hash, num_hashes: u64, transactions: Vec Transaction { - let pubkey = keypair.pubkey(); - let budget_contract = Keypair::new(); - let budget_pubkey = budget_contract.pubkey(); - let ixs = budget_instruction::payment(&pubkey, &pubkey, &budget_pubkey, 1); - let message = Message::new(&ixs, Some(&pubkey)); - Transaction::new(&[keypair, &budget_contract], message, hash) - } - - fn create_sample_timestamp(keypair: &Keypair, hash: Hash) -> Transaction { - let pubkey = keypair.pubkey(); - let ix = budget_instruction::apply_timestamp(&pubkey, &pubkey, &pubkey, Utc::now()); - let message = Message::new(&[ix], Some(&pubkey)); - Transaction::new(&[keypair], message, hash) - } - - fn create_sample_apply_signature(keypair: &Keypair, hash: Hash) -> Transaction { - let pubkey = keypair.pubkey(); - let ix = budget_instruction::apply_signature(&pubkey, &pubkey, &pubkey); - let message = Message::new(&[ix], Some(&pubkey)); - Transaction::new(&[keypair], message, hash) - } - #[test] fn test_entry_verify() { let zero = Hash::default(); @@ -819,23 +795,6 @@ mod tests { assert!(e0.verify(&zero)); } - #[test] - fn test_witness_reorder_attack() { - let zero = Hash::default(); - - // First, verify entries - let keypair = Keypair::new(); - let tx0 = create_sample_timestamp(&keypair, zero); - let tx1 = create_sample_apply_signature(&keypair, zero); - let mut e0 = Entry::new(&zero, 0, vec![tx0.clone(), tx1.clone()]); - assert!(e0.verify(&zero)); - - // Next, swap two witness transactions and ensure verification fails. - e0.transactions[0] = tx1; // <-- attack - e0.transactions[1] = tx0; - assert!(!e0.verify(&zero)); - } - #[test] fn test_next_entry() { let zero = Hash::default(); @@ -848,7 +807,7 @@ mod tests { assert_eq!(tick.hash, zero); let keypair = Keypair::new(); - let tx0 = create_sample_timestamp(&keypair, zero); + let tx0 = system_transaction::transfer(&keypair, &Pubkey::new_unique(), 42, zero); let entry0 = next_entry(&zero, 1, vec![tx0.clone()]); assert_eq!(entry0.num_hashes, 1); assert_eq!(entry0.hash, next_hash(&zero, 1, &[tx0])); @@ -904,9 +863,10 @@ mod tests { let zero = Hash::default(); let one = hash(&zero.as_ref()); let two = hash(&one.as_ref()); - let alice_pubkey = Keypair::new(); - let tx0 = create_sample_payment(&alice_pubkey, one); - let tx1 = create_sample_timestamp(&alice_pubkey, one); + let alice_keypair = Keypair::new(); + let bob_keypair = Keypair::new(); + let tx0 = system_transaction::transfer(&alice_keypair, &bob_keypair.pubkey(), 1, one); + let tx1 = system_transaction::transfer(&bob_keypair, &alice_keypair.pubkey(), 1, one); assert!(vec![][..].verify(&one)); // base case assert!(vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&one)); // singleton case 1 assert!(!vec![next_entry(&one, 1, vec![tx0.clone()])][..].verify(&two)); // singleton case 2, bad @@ -931,17 +891,14 @@ mod tests { let recent_blockhash = hash_new_rand(&mut rng); let keypair = Keypair::new(); let pubkey = keypair.pubkey(); - let budget_contract = Keypair::new(); - let budget_pubkey = budget_contract.pubkey(); let make_transaction = |size| { let ixs: Vec<_> = std::iter::repeat_with(|| { - budget_instruction::payment(&pubkey, &pubkey, &budget_pubkey, 1) + system_instruction::transfer(&pubkey, &Pubkey::new_unique(), 1) }) .take(size) - .flat_map(|x| x.into_iter()) .collect(); let message = Message::new(&ixs[..], Some(&pubkey)); - Transaction::new(&[&keypair, &budget_contract], message, recent_blockhash) + Transaction::new(&[&keypair], message, recent_blockhash) }; // Small transaction. { @@ -954,7 +911,7 @@ mod tests { } // Big transaction. { - let tx = make_transaction(15); + let tx = make_transaction(25); let entries = vec![next_entry(&recent_blockhash, 1, vec![tx.clone()])]; assert!(bincode::serialized_size(&tx).unwrap() > PACKET_DATA_SIZE as u64); assert!(entries[..] @@ -963,7 +920,7 @@ mod tests { } // Assert that verify fails as soon as serialized // size exceeds packet data size. - for size in 1..20 { + for size in 1..30 { let tx = make_transaction(size); let entries = vec![next_entry(&recent_blockhash, 1, vec![tx.clone()])]; assert_eq!( diff --git a/perf/Cargo.toml b/perf/Cargo.toml index 37bb39a86d..dfbe6f61c0 100644 --- a/perf/Cargo.toml +++ b/perf/Cargo.toml @@ -10,20 +10,20 @@ documentation = "https://docs.rs/solana-perf" edition = "2018" [dependencies] -rand = "0.7.0" -dlopen = "0.1.8" bincode = "1.3.1" -rayon = "1.5.0" -serde = "1.0.126" +curve25519-dalek = { version = "2" } +dlopen = "0.1.8" dlopen_derive = "0.1.4" lazy_static = "1.4.0" log = "0.4.11" -solana-sdk = { path = "../sdk", version = "=1.8.0" } -solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.8.0" } -solana-budget-program = { path = "../programs/budget", version = "=1.8.0" } +rand = "0.7.0" +rayon = "1.5.0" +serde = "1.0.126" solana-logger = { path = "../logger", version = "=1.8.0" } solana-metrics = { path = "../metrics", version = "=1.8.0" } -curve25519-dalek = { version = "2" } +solana-sdk = { path = "../sdk", version = "=1.8.0" } +solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.8.0" } +solana-stake-program = { path = "../programs/stake", version = "=1.8.0" } [lib] name = "solana_perf" diff --git a/perf/src/test_tx.rs b/perf/src/test_tx.rs index 6664f684f9..a571ec9f85 100644 --- a/perf/src/test_tx.rs +++ b/perf/src/test_tx.rs @@ -22,7 +22,7 @@ pub fn test_multisig_tx() -> Transaction { let transfer_instruction = SystemInstruction::Transfer { lamports }; - let program_ids = vec![system_program::id(), solana_budget_program::id()]; + let program_ids = vec![system_program::id(), solana_stake_program::id()]; let instructions = vec![CompiledInstruction::new( 0, diff --git a/programs/budget/Cargo.toml b/programs/budget/Cargo.toml deleted file mode 100644 index d4359d8943..0000000000 --- a/programs/budget/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "solana-budget-program" -version = "1.8.0" -description = "Solana Budget program" -authors = ["Solana Maintainers "] -repository = "https://github.com/solana-labs/solana" -license = "Apache-2.0" -homepage = "https://solana.com/" -documentation = "https://docs.rs/solana-budget-program" -edition = "2018" - -[dependencies] -bincode = "1.3.1" -chrono = { version = "0.4.11", features = ["serde"] } -log = "0.4.11" -num-derive = "0.3" -num-traits = "0.2" -serde = "1.0.126" -serde_derive = "1.0.103" -solana-sdk = { path = "../../sdk", version = "=1.8.0" } -thiserror = "1.0" - -[dev-dependencies] -solana-runtime = { path = "../../runtime", version = "=1.8.0" } - -[lib] -crate-type = ["lib", "cdylib"] -name = "solana_budget_program" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/budget/src/budget_expr.rs b/programs/budget/src/budget_expr.rs deleted file mode 100644 index c5584eee09..0000000000 --- a/programs/budget/src/budget_expr.rs +++ /dev/null @@ -1,390 +0,0 @@ -//! 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. - -use chrono::prelude::*; -use serde_derive::{Deserialize, Serialize}; -use solana_sdk::hash::Hash; -use solana_sdk::pubkey::Pubkey; - -/// The types of events a payment plan can process. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub enum Witness { - /// The current time. - Timestamp(DateTime), - - /// A signature from Pubkey. - Signature, - - /// Account snapshot. - AccountData(Hash, Pubkey), -} - -/// Some amount of lamports that should be sent to the `to` `Pubkey`. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct Payment { - /// Amount to be paid. - pub lamports: u64, - - /// The `Pubkey` that `lamports` should be paid to. - pub to: Pubkey, -} - -/// The account constraints a Condition would wait on. -/// Note: ideally this would be function that accepts an Account and returns -/// a bool, but we don't have a way to pass functions over the wire. To simulate -/// higher order programming, create your own program that includes an instruction -/// that sets account data to a boolean. Pass that account key and program_id here. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct AccountConstraints { - /// The account holder. - pub key: Pubkey, - - /// The program id that must own the account at `key`. - pub program_id: Pubkey, - - /// The hash of the data in the account at `key`. - pub data_hash: Hash, -} - -/// A data type representing a `Witness` that the payment plan is waiting on. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub enum Condition { - /// Wait for a `Timestamp` `Witness` at or after the given `DateTime`. - Timestamp(DateTime, Pubkey), - - /// Wait for a `Signature` `Witness` from `Pubkey`. - Signature(Pubkey), - - /// Wait for the account with the given constraints. - AccountData(AccountConstraints), -} - -impl Condition { - /// Return true if the given Witness satisfies this Condition. - pub fn is_satisfied(&self, witness: &Witness, from: &Pubkey) -> bool { - match (self, witness) { - (Condition::Signature(pubkey), Witness::Signature) => pubkey == from, - (Condition::Timestamp(dt, pubkey), Witness::Timestamp(last_time)) => { - pubkey == from && dt <= last_time - } - ( - Condition::AccountData(constraints), - Witness::AccountData(actual_hash, program_id), - ) => { - constraints.program_id == *program_id - && constraints.key == *from - && constraints.data_hash == *actual_hash - } - _ => false, - } - } -} - -/// A data type representing a payment plan. -#[repr(C)] -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub enum BudgetExpr { - /// Make a payment. - Pay(Payment), - - /// Make a payment after some condition. - After(Condition, Box), - - /// Either make a payment after one condition or a different payment after another - /// condition, which ever condition is satisfied first. - Or((Condition, Box), (Condition, Box)), - - /// Make a payment after both of two conditions are satisfied - And(Condition, Condition, Box), -} - -impl BudgetExpr { - /// Create the simplest budget - one that pays `lamports` to Pubkey. - pub fn new_payment(lamports: u64, to: &Pubkey) -> Self { - BudgetExpr::Pay(Payment { lamports, to: *to }) - } - - /// Create a budget that pays `lamports` to `to` after being witnessed by `from`. - pub fn new_authorized_payment(from: &Pubkey, lamports: u64, to: &Pubkey) -> Self { - BudgetExpr::After( - Condition::Signature(*from), - Box::new(Self::new_payment(lamports, to)), - ) - } - - /// Create a budget that pays `lamports` to `to` after witnessing account data in `account_pubkey` with the given hash. - pub fn new_payment_when_account_data( - account_pubkey: &Pubkey, - account_program_id: &Pubkey, - account_hash: Hash, - lamports: u64, - to: &Pubkey, - ) -> Self { - BudgetExpr::After( - Condition::AccountData(AccountConstraints { - key: *account_pubkey, - program_id: *account_program_id, - data_hash: account_hash, - }), - Box::new(Self::new_payment(lamports, to)), - ) - } - - /// Create a budget that pays `lamports` to `to` after being witnessed by `witness` unless - /// canceled with a signature from `from`. - pub fn new_cancelable_authorized_payment( - witness: &Pubkey, - lamports: u64, - to: &Pubkey, - from: Option, - ) -> Self { - if from.is_none() { - return Self::new_authorized_payment(witness, lamports, to); - } - let from = from.unwrap(); - BudgetExpr::Or( - ( - Condition::Signature(*witness), - Box::new(BudgetExpr::new_payment(lamports, to)), - ), - ( - Condition::Signature(from), - Box::new(BudgetExpr::new_payment(lamports, &from)), - ), - ) - } - - /// Create a budget that pays lamports` to `to` after being witnessed by 2x `from`s - pub fn new_2_2_multisig_payment( - from0: &Pubkey, - from1: &Pubkey, - lamports: u64, - to: &Pubkey, - ) -> Self { - BudgetExpr::And( - Condition::Signature(*from0), - Condition::Signature(*from1), - Box::new(Self::new_payment(lamports, to)), - ) - } - - /// Create a budget that pays `lamports` to `to` after the given DateTime signed - /// by `dt_pubkey`. - pub fn new_future_payment( - dt: DateTime, - dt_pubkey: &Pubkey, - lamports: u64, - to: &Pubkey, - ) -> Self { - BudgetExpr::After( - Condition::Timestamp(dt, *dt_pubkey), - Box::new(Self::new_payment(lamports, to)), - ) - } - - /// Create a budget that pays `lamports` to `to` after the given DateTime - /// signed by `dt_pubkey` unless canceled by `from`. - pub fn new_cancelable_future_payment( - dt: DateTime, - dt_pubkey: &Pubkey, - lamports: u64, - to: &Pubkey, - from: Option, - ) -> Self { - if from.is_none() { - return Self::new_future_payment(dt, dt_pubkey, lamports, to); - } - let from = from.unwrap(); - BudgetExpr::Or( - ( - Condition::Timestamp(dt, *dt_pubkey), - Box::new(Self::new_payment(lamports, to)), - ), - ( - Condition::Signature(from), - Box::new(Self::new_payment(lamports, &from)), - ), - ) - } - - /// Return Payment if the budget requires no additional Witnesses. - pub fn final_payment(&self) -> Option { - match self { - BudgetExpr::Pay(payment) => Some(payment.clone()), - _ => None, - } - } - - /// Return true if the budget spends exactly `spendable_lamports`. - pub fn verify(&self, spendable_lamports: u64) -> bool { - match self { - BudgetExpr::Pay(payment) => payment.lamports == spendable_lamports, - BudgetExpr::After(_, sub_expr) | BudgetExpr::And(_, _, sub_expr) => { - sub_expr.verify(spendable_lamports) - } - BudgetExpr::Or(a, b) => { - a.1.verify(spendable_lamports) && b.1.verify(spendable_lamports) - } - } - } - - /// 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_expr = match self { - BudgetExpr::After(cond, sub_expr) if cond.is_satisfied(witness, from) => { - Some(sub_expr.clone()) - } - BudgetExpr::Or((cond, sub_expr), _) if cond.is_satisfied(witness, from) => { - Some(sub_expr.clone()) - } - BudgetExpr::Or(_, (cond, sub_expr)) if cond.is_satisfied(witness, from) => { - Some(sub_expr.clone()) - } - BudgetExpr::And(cond0, cond1, sub_expr) => { - if cond0.is_satisfied(witness, from) { - Some(Box::new(BudgetExpr::After(cond1.clone(), sub_expr.clone()))) - } else if cond1.is_satisfied(witness, from) { - Some(Box::new(BudgetExpr::After(cond0.clone(), sub_expr.clone()))) - } else { - None - } - } - _ => None, - }; - if let Some(expr) = new_expr { - *self = *expr; - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_signature_satisfied() { - let from = Pubkey::default(); - assert!(Condition::Signature(from).is_satisfied(&Witness::Signature, &from)); - } - - #[test] - fn test_timestamp_satisfied() { - let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); - let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8); - let from = Pubkey::default(); - assert!(Condition::Timestamp(dt1, from).is_satisfied(&Witness::Timestamp(dt1), &from)); - assert!(Condition::Timestamp(dt1, from).is_satisfied(&Witness::Timestamp(dt2), &from)); - assert!(!Condition::Timestamp(dt2, from).is_satisfied(&Witness::Timestamp(dt1), &from)); - } - - #[test] - fn test_verify() { - let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); - let from = Pubkey::default(); - let to = Pubkey::default(); - 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, Some(from)).verify(42) - ); - } - - #[test] - fn test_authorized_payment() { - let from = Pubkey::default(); - let to = Pubkey::default(); - - 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] - fn test_future_payment() { - let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); - let from = solana_sdk::pubkey::new_rand(); - let to = solana_sdk::pubkey::new_rand(); - - 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] - fn test_unauthorized_future_payment() { - // Ensure timestamp will only be acknowledged if it came from the - // whitelisted public key. - let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); - let from = solana_sdk::pubkey::new_rand(); - let to = solana_sdk::pubkey::new_rand(); - - 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] - fn test_cancelable_future_payment() { - let dt = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10); - let from = Pubkey::default(); - let to = Pubkey::default(); - - let mut expr = BudgetExpr::new_cancelable_future_payment(dt, &from, 42, &to, Some(from)); - expr.apply_witness(&Witness::Timestamp(dt), &from); - assert_eq!(expr, BudgetExpr::new_payment(42, &to)); - - let mut expr = BudgetExpr::new_cancelable_future_payment(dt, &from, 42, &to, Some(from)); - expr.apply_witness(&Witness::Signature, &from); - assert_eq!(expr, BudgetExpr::new_payment(42, &from)); - } - #[test] - fn test_2_2_multisig_payment() { - let from0 = solana_sdk::pubkey::new_rand(); - let from1 = solana_sdk::pubkey::new_rand(); - let to = Pubkey::default(); - - 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)); - } - - #[test] - fn test_multisig_after_sig() { - let from0 = solana_sdk::pubkey::new_rand(); - let from1 = solana_sdk::pubkey::new_rand(); - let from2 = solana_sdk::pubkey::new_rand(); - let to = Pubkey::default(); - - let expr = BudgetExpr::new_2_2_multisig_payment(&from0, &from1, 42, &to); - let mut expr = BudgetExpr::After(Condition::Signature(from2), Box::new(expr)); - - expr.apply_witness(&Witness::Signature, &from2); - expr.apply_witness(&Witness::Signature, &from0); - assert_eq!(expr, BudgetExpr::new_authorized_payment(&from1, 42, &to)); - } - - #[test] - fn test_multisig_after_ts() { - let from0 = solana_sdk::pubkey::new_rand(); - let from1 = solana_sdk::pubkey::new_rand(); - let dt = Utc.ymd(2014, 11, 11).and_hms(7, 7, 7); - let to = Pubkey::default(); - - let expr = BudgetExpr::new_2_2_multisig_payment(&from0, &from1, 42, &to); - let mut expr = BudgetExpr::After(Condition::Timestamp(dt, from0), Box::new(expr)); - - expr.apply_witness(&Witness::Timestamp(dt), &from0); - assert_eq!( - 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)); - } -} diff --git a/programs/budget/src/budget_instruction.rs b/programs/budget/src/budget_instruction.rs deleted file mode 100644 index e61bdfcce0..0000000000 --- a/programs/budget/src/budget_instruction.rs +++ /dev/null @@ -1,195 +0,0 @@ -use crate::{budget_expr::BudgetExpr, budget_state::BudgetState, id}; -use bincode::serialized_size; -use chrono::prelude::{DateTime, Utc}; -use num_derive::{FromPrimitive, ToPrimitive}; -use serde_derive::{Deserialize, Serialize}; -use solana_sdk::{ - decode_error::DecodeError, - hash::Hash, - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_instruction, -}; -use thiserror::Error; - -#[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)] -pub enum BudgetError { - #[error("destination missing")] - DestinationMissing, -} - -impl DecodeError for BudgetError { - fn type_of() -> &'static str { - "BudgetError" - } -} - -/// An instruction to progress the smart contract. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub enum BudgetInstruction { - /// Declare and instantiate `BudgetExpr`. - InitializeAccount(Box), - - /// Tell a payment plan acknowledge the given `DateTime` has past. - ApplyTimestamp(DateTime), - - /// Tell the budget that the `InitializeAccount` with `Signature` has been - /// signed by the containing transaction's `Pubkey`. - ApplySignature, - - /// Load an account and pass its data to the budget for inspection. - ApplyAccountData, -} - -fn initialize_account(contract: &Pubkey, expr: BudgetExpr) -> Instruction { - let mut keys = vec![]; - if let BudgetExpr::Pay(payment) = &expr { - keys.push(AccountMeta::new(payment.to, false)); - } - keys.push(AccountMeta::new(*contract, false)); - Instruction::new_with_bincode( - id(), - &BudgetInstruction::InitializeAccount(Box::new(expr)), - keys, - ) -} - -pub fn create_account( - from: &Pubkey, - contract: &Pubkey, - lamports: u64, - expr: BudgetExpr, -) -> Vec { - if !expr.verify(lamports) { - panic!("invalid budget expression"); - } - let space = serialized_size(&BudgetState::new(expr.clone())).unwrap(); - vec![ - system_instruction::create_account(&from, contract, lamports, space, &id()), - initialize_account(contract, expr), - ] -} - -/// Create a new payment script. -pub fn payment(from: &Pubkey, to: &Pubkey, contract: &Pubkey, lamports: u64) -> Vec { - let expr = BudgetExpr::new_payment(lamports, to); - create_account(from, &contract, lamports, expr) -} - -/// Create a future payment script. -pub fn on_date( - from: &Pubkey, - to: &Pubkey, - contract: &Pubkey, - dt: DateTime, - dt_pubkey: &Pubkey, - cancelable: Option, - lamports: u64, -) -> Vec { - let expr = BudgetExpr::new_cancelable_future_payment(dt, dt_pubkey, lamports, to, cancelable); - create_account(from, contract, lamports, expr) -} - -/// Create a multisig payment script. -pub fn when_signed( - from: &Pubkey, - to: &Pubkey, - contract: &Pubkey, - witness: &Pubkey, - cancelable: Option, - lamports: u64, -) -> Vec { - let expr = BudgetExpr::new_cancelable_authorized_payment(witness, lamports, to, cancelable); - create_account(from, contract, lamports, expr) -} - -/// Make a payment when an account has the given data -pub fn when_account_data( - from: &Pubkey, - to: &Pubkey, - contract: &Pubkey, - account_pubkey: &Pubkey, - account_program_id: &Pubkey, - account_hash: Hash, - lamports: u64, -) -> Vec { - let expr = BudgetExpr::new_payment_when_account_data( - account_pubkey, - account_program_id, - account_hash, - lamports, - to, - ); - create_account(from, contract, lamports, expr) -} - -pub fn apply_timestamp( - from: &Pubkey, - contract: &Pubkey, - to: &Pubkey, - dt: DateTime, -) -> Instruction { - let mut account_metas = vec![ - AccountMeta::new(*from, true), - AccountMeta::new(*contract, false), - ]; - if from != to { - account_metas.push(AccountMeta::new(*to, false)); - } - Instruction::new_with_bincode(id(), &BudgetInstruction::ApplyTimestamp(dt), account_metas) -} - -pub fn apply_signature(from: &Pubkey, contract: &Pubkey, to: &Pubkey) -> Instruction { - let mut account_metas = vec![ - AccountMeta::new(*from, true), - AccountMeta::new(*contract, false), - ]; - if from != to { - account_metas.push(AccountMeta::new(*to, false)); - } - Instruction::new_with_bincode(id(), &BudgetInstruction::ApplySignature, account_metas) -} - -/// Apply account data to a contract waiting on an AccountData witness. -pub fn apply_account_data(witness_pubkey: &Pubkey, contract: &Pubkey, to: &Pubkey) -> Instruction { - let account_metas = vec![ - AccountMeta::new_readonly(*witness_pubkey, false), - AccountMeta::new(*contract, false), - AccountMeta::new(*to, false), - ]; - Instruction::new_with_bincode(id(), &BudgetInstruction::ApplyAccountData, account_metas) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::budget_expr::BudgetExpr; - - #[test] - fn test_budget_instruction_verify() { - let alice_pubkey = solana_sdk::pubkey::new_rand(); - let bob_pubkey = solana_sdk::pubkey::new_rand(); - let budget_pubkey = solana_sdk::pubkey::new_rand(); - payment(&alice_pubkey, &bob_pubkey, &budget_pubkey, 1); // No panic! indicates success. - } - - #[test] - #[should_panic] - fn test_budget_instruction_overspend() { - let alice_pubkey = solana_sdk::pubkey::new_rand(); - let bob_pubkey = solana_sdk::pubkey::new_rand(); - let budget_pubkey = solana_sdk::pubkey::new_rand(); - let expr = BudgetExpr::new_payment(2, &bob_pubkey); - create_account(&alice_pubkey, &budget_pubkey, 1, expr); - } - - #[test] - #[should_panic] - fn test_budget_instruction_underspend() { - let alice_pubkey = solana_sdk::pubkey::new_rand(); - let bob_pubkey = solana_sdk::pubkey::new_rand(); - let budget_pubkey = solana_sdk::pubkey::new_rand(); - let expr = BudgetExpr::new_payment(1, &bob_pubkey); - create_account(&alice_pubkey, &budget_pubkey, 2, expr); - } -} diff --git a/programs/budget/src/budget_processor.rs b/programs/budget/src/budget_processor.rs deleted file mode 100644 index 1df0ea8592..0000000000 --- a/programs/budget/src/budget_processor.rs +++ /dev/null @@ -1,627 +0,0 @@ -//! budget program -use crate::{ - budget_expr::Witness, - budget_instruction::{BudgetError, BudgetInstruction}, - budget_state::BudgetState, -}; -use chrono::prelude::{DateTime, Utc}; -use log::*; -use solana_sdk::{ - account::{ReadableAccount, WritableAccount}, - hash::hash, - instruction::InstructionError, - keyed_account::{keyed_account_at_index, KeyedAccount}, - process_instruction::InvokeContext, - program_utils::limited_deserialize, - pubkey::Pubkey, -}; - -/// Process a Witness Signature. Any payment plans waiting on this signature -/// will progress one step. -fn apply_signature( - budget_state: &mut BudgetState, - witness_keyed_account: &KeyedAccount, - contract_keyed_account: &KeyedAccount, - to_keyed_account: Result<&KeyedAccount, InstructionError>, -) -> Result<(), InstructionError> { - let mut final_payment = None; - if let Some(ref mut expr) = budget_state.pending_budget { - let key = witness_keyed_account.signer_key().unwrap(); - expr.apply_witness(&Witness::Signature, key); - final_payment = expr.final_payment(); - } - - if let Some(payment) = final_payment { - if let Some(key) = witness_keyed_account.signer_key() { - if &payment.to == key { - budget_state.pending_budget = None; - contract_keyed_account - .try_account_ref_mut()? - .checked_sub_lamports(payment.lamports)?; - witness_keyed_account - .try_account_ref_mut()? - .checked_add_lamports(payment.lamports)?; - return Ok(()); - } - } - let to_keyed_account = to_keyed_account?; - if &payment.to != to_keyed_account.unsigned_key() { - trace!("destination missing"); - return Err(BudgetError::DestinationMissing.into()); - } - budget_state.pending_budget = None; - contract_keyed_account - .try_account_ref_mut()? - .checked_sub_lamports(payment.lamports)?; - to_keyed_account - .try_account_ref_mut()? - .checked_add_lamports(payment.lamports)?; - } - Ok(()) -} - -/// Process a Witness Timestamp. Any payment plans waiting on this timestamp -/// will progress one step. -fn apply_timestamp( - budget_state: &mut BudgetState, - witness_keyed_account: &KeyedAccount, - contract_keyed_account: &KeyedAccount, - to_keyed_account: Result<&KeyedAccount, InstructionError>, - dt: DateTime, -) -> Result<(), InstructionError> { - // Check to see if any timelocked transactions can be completed. - let mut final_payment = None; - - if let Some(ref mut expr) = budget_state.pending_budget { - let key = witness_keyed_account.signer_key().unwrap(); - expr.apply_witness(&Witness::Timestamp(dt), key); - final_payment = expr.final_payment(); - } - - if let Some(payment) = final_payment { - let to_keyed_account = to_keyed_account?; - if &payment.to != to_keyed_account.unsigned_key() { - trace!("destination missing"); - return Err(BudgetError::DestinationMissing.into()); - } - budget_state.pending_budget = None; - contract_keyed_account - .try_account_ref_mut()? - .checked_sub_lamports(payment.lamports)?; - to_keyed_account - .try_account_ref_mut()? - .checked_add_lamports(payment.lamports)?; - } - Ok(()) -} - -/// Process an AccountData Witness and any payment waiting on it. -fn apply_account_data( - budget_state: &mut BudgetState, - witness_keyed_account: &KeyedAccount, - contract_keyed_account: &KeyedAccount, - to_keyed_account: Result<&KeyedAccount, InstructionError>, -) -> Result<(), InstructionError> { - // Check to see if any timelocked transactions can be completed. - let mut final_payment = None; - - if let Some(ref mut expr) = budget_state.pending_budget { - let key = witness_keyed_account.unsigned_key(); - let program_id = witness_keyed_account.owner()?; - let actual_hash = hash(&witness_keyed_account.try_account_ref()?.data()); - expr.apply_witness(&Witness::AccountData(actual_hash, program_id), key); - final_payment = expr.final_payment(); - } - - if let Some(payment) = final_payment { - let to_keyed_account = to_keyed_account?; - if &payment.to != to_keyed_account.unsigned_key() { - trace!("destination missing"); - return Err(BudgetError::DestinationMissing.into()); - } - budget_state.pending_budget = None; - contract_keyed_account - .try_account_ref_mut()? - .checked_sub_lamports(payment.lamports)?; - to_keyed_account - .try_account_ref_mut()? - .checked_add_lamports(payment.lamports)?; - } - Ok(()) -} - -pub fn process_instruction( - _program_id: &Pubkey, - data: &[u8], - invoke_context: &mut dyn InvokeContext, -) -> Result<(), InstructionError> { - let keyed_accounts = invoke_context.get_keyed_accounts()?; - - let instruction = limited_deserialize(data)?; - - trace!("process_instruction: {:?}", instruction); - - match instruction { - BudgetInstruction::InitializeAccount(expr) => { - let contract_keyed_account = keyed_account_at_index(keyed_accounts, 0)?; - - if let Some(payment) = expr.final_payment() { - let to_keyed_account = contract_keyed_account; - let contract_keyed_account = keyed_account_at_index(keyed_accounts, 1)?; - contract_keyed_account - .try_account_ref_mut()? - .set_lamports(0); - to_keyed_account - .try_account_ref_mut()? - .checked_add_lamports(payment.lamports)?; - return Ok(()); - } - let existing = - BudgetState::deserialize(&contract_keyed_account.try_account_ref_mut()?.data()) - .ok(); - if Some(true) == existing.map(|x| x.initialized) { - trace!("contract already exists"); - return Err(InstructionError::AccountAlreadyInitialized); - } - let budget_state = BudgetState { - pending_budget: Some(*expr), - initialized: true, - }; - budget_state.serialize( - &mut contract_keyed_account - .try_account_ref_mut()? - .data_as_mut_slice(), - ) - } - BudgetInstruction::ApplyTimestamp(dt) => { - let witness_keyed_account = keyed_account_at_index(keyed_accounts, 0)?; - let contract_keyed_account = keyed_account_at_index(keyed_accounts, 1)?; - let mut budget_state = - BudgetState::deserialize(&contract_keyed_account.try_account_ref()?.data())?; - if !budget_state.is_pending() { - return Ok(()); // Nothing to do here. - } - if !budget_state.initialized { - trace!("contract is uninitialized"); - return Err(InstructionError::UninitializedAccount); - } - if witness_keyed_account.signer_key().is_none() { - return Err(InstructionError::MissingRequiredSignature); - } - trace!("apply timestamp"); - apply_timestamp( - &mut budget_state, - witness_keyed_account, - contract_keyed_account, - keyed_account_at_index(keyed_accounts, 2), - dt, - )?; - trace!("apply timestamp committed"); - budget_state.serialize( - &mut contract_keyed_account - .try_account_ref_mut()? - .data_as_mut_slice(), - ) - } - BudgetInstruction::ApplySignature => { - let witness_keyed_account = keyed_account_at_index(keyed_accounts, 0)?; - let contract_keyed_account = keyed_account_at_index(keyed_accounts, 1)?; - let mut budget_state = - BudgetState::deserialize(&contract_keyed_account.try_account_ref()?.data())?; - if !budget_state.is_pending() { - return Ok(()); // Nothing to do here. - } - if !budget_state.initialized { - trace!("contract is uninitialized"); - return Err(InstructionError::UninitializedAccount); - } - if witness_keyed_account.signer_key().is_none() { - return Err(InstructionError::MissingRequiredSignature); - } - trace!("apply signature"); - apply_signature( - &mut budget_state, - witness_keyed_account, - contract_keyed_account, - keyed_account_at_index(keyed_accounts, 2), - )?; - trace!("apply signature committed"); - budget_state.serialize( - &mut contract_keyed_account - .try_account_ref_mut()? - .data_as_mut_slice(), - ) - } - BudgetInstruction::ApplyAccountData => { - let witness_keyed_account = keyed_account_at_index(keyed_accounts, 0)?; - let contract_keyed_account = keyed_account_at_index(keyed_accounts, 1)?; - let mut budget_state = - BudgetState::deserialize(&contract_keyed_account.try_account_ref()?.data())?; - if !budget_state.is_pending() { - return Ok(()); // Nothing to do here. - } - if !budget_state.initialized { - trace!("contract is uninitialized"); - return Err(InstructionError::UninitializedAccount); - } - apply_account_data( - &mut budget_state, - witness_keyed_account, - contract_keyed_account, - keyed_account_at_index(keyed_accounts, 2), - )?; - trace!("apply account data committed"); - budget_state.serialize( - &mut contract_keyed_account - .try_account_ref_mut()? - .data_as_mut_slice(), - ) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::budget_instruction; - use crate::id; - use solana_runtime::bank::Bank; - use solana_runtime::bank_client::BankClient; - use solana_sdk::account::{Account, AccountSharedData}; - use solana_sdk::client::SyncClient; - use solana_sdk::genesis_config::create_genesis_config; - use solana_sdk::hash::hash; - use solana_sdk::instruction::InstructionError; - use solana_sdk::message::Message; - use solana_sdk::signature::{Keypair, Signer}; - use solana_sdk::transaction::TransactionError; - - fn create_bank(lamports: u64) -> (Bank, Keypair) { - let (genesis_config, mint_keypair) = create_genesis_config(lamports); - let mut bank = Bank::new(&genesis_config); - bank.add_builtin("budget_program", id(), process_instruction); - (bank, mint_keypair) - } - - #[test] - fn test_initialize_no_panic() { - let (bank, alice_keypair) = create_bank(1); - let bank_client = BankClient::new(bank); - - let alice_pubkey = alice_keypair.pubkey(); - let budget_keypair = Keypair::new(); - let budget_pubkey = budget_keypair.pubkey(); - let bob_pubkey = solana_sdk::pubkey::new_rand(); - - let mut instructions = - budget_instruction::payment(&alice_pubkey, &bob_pubkey, &budget_pubkey, 1); - instructions[1].accounts = vec![]; //