2018-09-19 17:25:57 -07:00
|
|
|
//! budget program
|
2019-03-03 13:17:51 -08:00
|
|
|
use bincode::deserialize;
|
2018-09-17 13:36:31 -07:00
|
|
|
use chrono::prelude::{DateTime, Utc};
|
2018-12-14 20:39:10 -08:00
|
|
|
use log::*;
|
2019-03-03 13:17:51 -08:00
|
|
|
use solana_budget_api::budget_instruction::BudgetInstruction;
|
2019-03-03 14:43:51 -08:00
|
|
|
use solana_budget_api::budget_state::{BudgetError, BudgetState};
|
2019-03-02 13:23:22 -08:00
|
|
|
use solana_budget_api::payment_plan::Witness;
|
2018-12-04 14:38:19 -08:00
|
|
|
use solana_sdk::account::KeyedAccount;
|
2019-03-03 13:17:51 -08:00
|
|
|
|
|
|
|
/// Process a Witness Signature. Any payment plans waiting on this signature
|
|
|
|
/// will progress one step.
|
|
|
|
fn apply_signature(
|
|
|
|
budget_state: &mut BudgetState,
|
|
|
|
keyed_accounts: &mut [KeyedAccount],
|
|
|
|
) -> Result<(), BudgetError> {
|
|
|
|
let mut final_payment = None;
|
|
|
|
if let Some(ref mut expr) = budget_state.pending_budget {
|
|
|
|
let key = match keyed_accounts[0].signer_key() {
|
|
|
|
None => return Err(BudgetError::UnsignedKey),
|
|
|
|
Some(key) => key,
|
|
|
|
};
|
|
|
|
expr.apply_witness(&Witness::Signature, key);
|
|
|
|
final_payment = expr.final_payment();
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(payment) = final_payment {
|
|
|
|
if let Some(key) = keyed_accounts[0].signer_key() {
|
|
|
|
if &payment.to == key {
|
|
|
|
budget_state.pending_budget = None;
|
|
|
|
keyed_accounts[1].account.tokens -= payment.tokens;
|
|
|
|
keyed_accounts[0].account.tokens += payment.tokens;
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if &payment.to != keyed_accounts[2].unsigned_key() {
|
|
|
|
trace!("destination missing");
|
|
|
|
return Err(BudgetError::DestinationMissing);
|
|
|
|
}
|
|
|
|
budget_state.pending_budget = None;
|
|
|
|
keyed_accounts[1].account.tokens -= payment.tokens;
|
|
|
|
keyed_accounts[2].account.tokens += payment.tokens;
|
|
|
|
}
|
|
|
|
Ok(())
|
2018-09-17 13:36:31 -07:00
|
|
|
}
|
|
|
|
|
2019-03-03 13:17:51 -08:00
|
|
|
/// Process a Witness Timestamp. Any payment plans waiting on this timestamp
|
|
|
|
/// will progress one step.
|
|
|
|
fn apply_timestamp(
|
|
|
|
budget_state: &mut BudgetState,
|
|
|
|
keyed_accounts: &mut [KeyedAccount],
|
|
|
|
dt: DateTime<Utc>,
|
|
|
|
) -> Result<(), BudgetError> {
|
|
|
|
// 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 = match keyed_accounts[0].signer_key() {
|
|
|
|
None => return Err(BudgetError::UnsignedKey),
|
|
|
|
Some(key) => key,
|
|
|
|
};
|
|
|
|
expr.apply_witness(&Witness::Timestamp(dt), key);
|
|
|
|
final_payment = expr.final_payment();
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(payment) = final_payment {
|
|
|
|
if &payment.to != keyed_accounts[2].unsigned_key() {
|
|
|
|
trace!("destination missing");
|
|
|
|
return Err(BudgetError::DestinationMissing);
|
|
|
|
}
|
|
|
|
budget_state.pending_budget = None;
|
|
|
|
keyed_accounts[1].account.tokens -= payment.tokens;
|
|
|
|
keyed_accounts[2].account.tokens += payment.tokens;
|
|
|
|
}
|
|
|
|
Ok(())
|
2018-09-17 13:36:31 -07:00
|
|
|
}
|
|
|
|
|
2018-11-23 12:45:34 -08:00
|
|
|
fn apply_debits(
|
2018-12-04 14:38:19 -08:00
|
|
|
keyed_accounts: &mut [KeyedAccount],
|
2019-03-03 13:17:51 -08:00
|
|
|
instruction: &BudgetInstruction,
|
2018-11-23 12:45:34 -08:00
|
|
|
) -> Result<(), BudgetError> {
|
2018-12-04 14:38:19 -08:00
|
|
|
if !keyed_accounts[0].account.userdata.is_empty() {
|
2018-11-23 12:45:34 -08:00
|
|
|
trace!("source is pending");
|
|
|
|
return Err(BudgetError::SourceIsPendingContract);
|
2018-09-17 13:36:31 -07:00
|
|
|
}
|
2018-11-23 12:45:34 -08:00
|
|
|
match instruction {
|
2019-03-03 13:17:51 -08:00
|
|
|
BudgetInstruction::InitializeAccount(expr) => {
|
2018-11-23 12:45:34 -08:00
|
|
|
let expr = expr.clone();
|
|
|
|
if let Some(payment) = expr.final_payment() {
|
2019-03-03 14:43:51 -08:00
|
|
|
keyed_accounts[1].account.tokens = 0;
|
|
|
|
keyed_accounts[0].account.tokens += payment.tokens;
|
2018-11-23 12:45:34 -08:00
|
|
|
Ok(())
|
|
|
|
} else {
|
2019-03-03 13:17:51 -08:00
|
|
|
let existing = BudgetState::deserialize(&keyed_accounts[1].account.userdata).ok();
|
2018-11-23 12:45:34 -08:00
|
|
|
if Some(true) == existing.map(|x| x.initialized) {
|
|
|
|
trace!("contract already exists");
|
|
|
|
Err(BudgetError::ContractAlreadyExists)
|
|
|
|
} else {
|
2019-03-03 13:17:51 -08:00
|
|
|
let mut budget_state = BudgetState::default();
|
|
|
|
budget_state.pending_budget = Some(expr);
|
2018-12-04 14:38:19 -08:00
|
|
|
keyed_accounts[1].account.tokens += keyed_accounts[0].account.tokens;
|
|
|
|
keyed_accounts[0].account.tokens = 0;
|
2019-03-03 13:17:51 -08:00
|
|
|
budget_state.initialized = true;
|
|
|
|
budget_state.serialize(&mut keyed_accounts[1].account.userdata)
|
2018-11-23 12:45:34 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-03-03 13:17:51 -08:00
|
|
|
BudgetInstruction::ApplyTimestamp(dt) => {
|
|
|
|
if let Ok(mut budget_state) =
|
|
|
|
BudgetState::deserialize(&keyed_accounts[1].account.userdata)
|
2018-12-04 14:38:19 -08:00
|
|
|
{
|
2019-03-03 13:17:51 -08:00
|
|
|
if !budget_state.is_pending() {
|
2018-11-23 12:45:34 -08:00
|
|
|
Err(BudgetError::ContractNotPending)
|
2019-03-03 13:17:51 -08:00
|
|
|
} else if !budget_state.initialized {
|
2018-11-23 12:45:34 -08:00
|
|
|
trace!("contract is uninitialized");
|
|
|
|
Err(BudgetError::UninitializedContract)
|
|
|
|
} else {
|
|
|
|
trace!("apply timestamp");
|
2019-03-03 13:17:51 -08:00
|
|
|
apply_timestamp(&mut budget_state, keyed_accounts, *dt)?;
|
2018-11-23 12:45:34 -08:00
|
|
|
trace!("apply timestamp committed");
|
2019-03-03 13:17:51 -08:00
|
|
|
budget_state.serialize(&mut keyed_accounts[1].account.userdata)
|
2018-11-23 12:45:34 -08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(BudgetError::UninitializedContract)
|
|
|
|
}
|
|
|
|
}
|
2019-03-03 13:17:51 -08:00
|
|
|
BudgetInstruction::ApplySignature => {
|
|
|
|
if let Ok(mut budget_state) =
|
|
|
|
BudgetState::deserialize(&keyed_accounts[1].account.userdata)
|
2018-12-04 14:38:19 -08:00
|
|
|
{
|
2019-03-03 13:17:51 -08:00
|
|
|
if !budget_state.is_pending() {
|
2018-11-23 12:45:34 -08:00
|
|
|
Err(BudgetError::ContractNotPending)
|
2019-03-03 13:17:51 -08:00
|
|
|
} else if !budget_state.initialized {
|
2018-11-23 12:45:34 -08:00
|
|
|
trace!("contract is uninitialized");
|
|
|
|
Err(BudgetError::UninitializedContract)
|
|
|
|
} else {
|
|
|
|
trace!("apply signature");
|
2019-03-03 13:17:51 -08:00
|
|
|
apply_signature(&mut budget_state, keyed_accounts)?;
|
2018-11-23 12:45:34 -08:00
|
|
|
trace!("apply signature committed");
|
2019-03-03 13:17:51 -08:00
|
|
|
budget_state.serialize(&mut keyed_accounts[1].account.userdata)
|
2018-11-23 12:45:34 -08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(BudgetError::UninitializedContract)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Budget DSL contract interface
|
|
|
|
/// * accounts[0] - The source of the tokens
|
|
|
|
/// * accounts[1] - The contract context. Once the contract has been completed, the tokens can
|
|
|
|
/// be spent from this account .
|
2018-11-23 14:14:13 -08:00
|
|
|
pub fn process_instruction(
|
2018-12-04 14:38:19 -08:00
|
|
|
keyed_accounts: &mut [KeyedAccount],
|
|
|
|
data: &[u8],
|
2018-11-23 12:45:34 -08:00
|
|
|
) -> Result<(), BudgetError> {
|
2018-12-04 14:38:19 -08:00
|
|
|
if let Ok(instruction) = deserialize(data) {
|
2018-11-23 14:14:13 -08:00
|
|
|
trace!("process_instruction: {:?}", instruction);
|
2018-12-04 14:38:19 -08:00
|
|
|
apply_debits(keyed_accounts, &instruction)
|
2018-11-23 12:45:34 -08:00
|
|
|
} else {
|
2018-12-04 14:38:19 -08:00
|
|
|
info!("Invalid transaction userdata: {:?}", data);
|
2018-11-23 12:45:34 -08:00
|
|
|
Err(BudgetError::UserdataDeserializeFailure)
|
2018-09-17 13:36:31 -07:00
|
|
|
}
|
2018-11-23 12:45:34 -08:00
|
|
|
}
|
|
|
|
|
2018-09-17 13:36:31 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2018-11-23 12:45:34 -08:00
|
|
|
use super::*;
|
2019-03-02 13:23:22 -08:00
|
|
|
use solana_budget_api::budget_transaction::BudgetTransaction;
|
|
|
|
use solana_budget_api::id;
|
2018-10-25 11:13:08 -07:00
|
|
|
use solana_sdk::account::Account;
|
2018-11-16 08:04:46 -08:00
|
|
|
use solana_sdk::hash::Hash;
|
2018-12-03 10:26:28 -08:00
|
|
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
2018-12-04 14:38:19 -08:00
|
|
|
use solana_sdk::transaction::{Instruction, Transaction};
|
2018-09-26 09:33:52 -07:00
|
|
|
|
2018-12-04 14:38:19 -08:00
|
|
|
fn process_transaction(
|
|
|
|
tx: &Transaction,
|
|
|
|
program_accounts: &mut [Account],
|
|
|
|
) -> Result<(), BudgetError> {
|
|
|
|
assert_eq!(tx.instructions.len(), 1);
|
|
|
|
let Instruction {
|
|
|
|
ref accounts,
|
|
|
|
ref userdata,
|
|
|
|
..
|
|
|
|
} = tx.instructions[0];
|
|
|
|
|
|
|
|
let mut keyed_accounts: Vec<_> = accounts
|
|
|
|
.iter()
|
|
|
|
.map(|&index| {
|
|
|
|
let index = index as usize;
|
|
|
|
let key = &tx.account_keys[index];
|
|
|
|
(key, index < tx.signatures.len())
|
2018-12-07 19:01:28 -08:00
|
|
|
})
|
|
|
|
.zip(program_accounts.iter_mut())
|
2018-12-04 14:38:19 -08:00
|
|
|
.map(|((key, is_signer), account)| KeyedAccount::new(key, is_signer, account))
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
super::process_instruction(&mut keyed_accounts, &userdata)
|
2018-09-28 16:16:35 -07:00
|
|
|
}
|
2018-09-17 13:36:31 -07:00
|
|
|
#[test]
|
2018-09-20 13:54:42 -07:00
|
|
|
fn test_invalid_instruction() {
|
2018-11-23 12:45:34 -08:00
|
|
|
let mut accounts = vec![Account::new(1, 0, id()), Account::new(0, 512, id())];
|
2018-09-20 13:54:42 -07:00
|
|
|
let from = Keypair::new();
|
|
|
|
let contract = Keypair::new();
|
2018-11-06 05:50:00 -08:00
|
|
|
let userdata = (1u8, 2u8, 3u8);
|
2018-09-26 08:54:04 -07:00
|
|
|
let tx = Transaction::new(
|
2018-09-20 13:54:42 -07:00
|
|
|
&from,
|
|
|
|
&[contract.pubkey()],
|
2018-11-23 12:45:34 -08:00
|
|
|
id(),
|
2018-11-06 05:50:00 -08:00
|
|
|
&userdata,
|
2018-09-20 13:54:42 -07:00
|
|
|
Hash::default(),
|
|
|
|
0,
|
|
|
|
);
|
2018-09-28 16:16:35 -07:00
|
|
|
assert!(process_transaction(&tx, &mut accounts).is_err());
|
2018-09-20 13:54:42 -07:00
|
|
|
}
|
2018-09-20 09:38:37 -07:00
|
|
|
|
2018-10-18 20:10:33 -07:00
|
|
|
#[test]
|
|
|
|
fn test_unsigned_witness_key() {
|
|
|
|
let mut accounts = vec![
|
2018-11-23 12:45:34 -08:00
|
|
|
Account::new(1, 0, id()),
|
|
|
|
Account::new(0, 512, id()),
|
|
|
|
Account::new(0, 0, id()),
|
2018-10-18 20:10:33 -07:00
|
|
|
];
|
|
|
|
|
2019-03-03 13:17:51 -08:00
|
|
|
// Initialize BudgetState
|
2018-10-18 20:10:33 -07:00
|
|
|
let from = Keypair::new();
|
|
|
|
let contract = Keypair::new().pubkey();
|
|
|
|
let to = Keypair::new().pubkey();
|
|
|
|
let witness = Keypair::new().pubkey();
|
2019-02-01 07:36:35 -08:00
|
|
|
let tx = BudgetTransaction::new_when_signed(
|
2018-10-18 20:10:33 -07:00
|
|
|
&from,
|
|
|
|
to,
|
|
|
|
contract,
|
|
|
|
witness,
|
|
|
|
None,
|
|
|
|
1,
|
|
|
|
Hash::default(),
|
|
|
|
);
|
|
|
|
process_transaction(&tx, &mut accounts).unwrap();
|
|
|
|
|
|
|
|
// Attack! Part 1: Sign a witness transaction with a random key.
|
|
|
|
let rando = Keypair::new();
|
2019-02-01 07:36:35 -08:00
|
|
|
let mut tx = BudgetTransaction::new_signature(&rando, contract, to, Hash::default());
|
2018-10-18 20:10:33 -07:00
|
|
|
|
|
|
|
// Attack! Part 2: Point the instruction to the expected, but unsigned, key.
|
|
|
|
tx.account_keys.push(from.pubkey());
|
|
|
|
tx.instructions[0].accounts[0] = 3;
|
|
|
|
|
|
|
|
// Ensure the transaction fails because of the unsigned key.
|
|
|
|
assert_eq!(
|
|
|
|
process_transaction(&tx, &mut accounts),
|
|
|
|
Err(BudgetError::UnsignedKey)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_unsigned_timestamp() {
|
|
|
|
let mut accounts = vec![
|
2018-11-23 12:45:34 -08:00
|
|
|
Account::new(1, 0, id()),
|
|
|
|
Account::new(0, 512, id()),
|
|
|
|
Account::new(0, 0, id()),
|
2018-10-18 20:10:33 -07:00
|
|
|
];
|
|
|
|
|
2019-03-03 13:17:51 -08:00
|
|
|
// Initialize BudgetState
|
2018-10-18 20:10:33 -07:00
|
|
|
let from = Keypair::new();
|
|
|
|
let contract = Keypair::new().pubkey();
|
|
|
|
let to = Keypair::new().pubkey();
|
|
|
|
let dt = Utc::now();
|
2019-02-01 07:36:35 -08:00
|
|
|
let tx = BudgetTransaction::new_on_date(
|
2018-10-18 20:10:33 -07:00
|
|
|
&from,
|
|
|
|
to,
|
|
|
|
contract,
|
|
|
|
dt,
|
|
|
|
from.pubkey(),
|
|
|
|
None,
|
|
|
|
1,
|
|
|
|
Hash::default(),
|
|
|
|
);
|
|
|
|
process_transaction(&tx, &mut accounts).unwrap();
|
|
|
|
|
|
|
|
// Attack! Part 1: Sign a timestamp transaction with a random key.
|
|
|
|
let rando = Keypair::new();
|
2019-02-01 07:36:35 -08:00
|
|
|
let mut tx = BudgetTransaction::new_timestamp(&rando, contract, to, dt, Hash::default());
|
2018-10-18 20:10:33 -07:00
|
|
|
|
|
|
|
// Attack! Part 2: Point the instruction to the expected, but unsigned, key.
|
|
|
|
tx.account_keys.push(from.pubkey());
|
|
|
|
tx.instructions[0].accounts[0] = 3;
|
|
|
|
|
|
|
|
// Ensure the transaction fails because of the unsigned key.
|
|
|
|
assert_eq!(
|
|
|
|
process_transaction(&tx, &mut accounts),
|
|
|
|
Err(BudgetError::UnsignedKey)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-09-17 13:36:31 -07:00
|
|
|
#[test]
|
|
|
|
fn test_transfer_on_date() {
|
|
|
|
let mut accounts = vec![
|
2018-11-23 12:45:34 -08:00
|
|
|
Account::new(1, 0, id()),
|
|
|
|
Account::new(0, 512, id()),
|
|
|
|
Account::new(0, 0, id()),
|
2018-09-17 13:36:31 -07:00
|
|
|
];
|
|
|
|
let from_account = 0;
|
|
|
|
let contract_account = 1;
|
|
|
|
let to_account = 2;
|
|
|
|
let from = Keypair::new();
|
|
|
|
let contract = Keypair::new();
|
|
|
|
let to = Keypair::new();
|
|
|
|
let rando = Keypair::new();
|
|
|
|
let dt = Utc::now();
|
2019-02-01 07:36:35 -08:00
|
|
|
let tx = BudgetTransaction::new_on_date(
|
2018-09-17 13:36:31 -07:00
|
|
|
&from,
|
|
|
|
to.pubkey(),
|
|
|
|
contract.pubkey(),
|
|
|
|
dt,
|
2018-09-19 16:44:03 -07:00
|
|
|
from.pubkey(),
|
2018-09-22 16:51:21 -07:00
|
|
|
None,
|
2018-09-17 13:36:31 -07:00
|
|
|
1,
|
|
|
|
Hash::default(),
|
|
|
|
);
|
2018-09-28 16:16:35 -07:00
|
|
|
process_transaction(&tx, &mut accounts).unwrap();
|
2018-09-17 13:36:31 -07:00
|
|
|
assert_eq!(accounts[from_account].tokens, 0);
|
|
|
|
assert_eq!(accounts[contract_account].tokens, 1);
|
2019-03-03 13:17:51 -08:00
|
|
|
let budget_state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap();
|
|
|
|
assert!(budget_state.is_pending());
|
2018-09-17 13:36:31 -07:00
|
|
|
|
|
|
|
// Attack! Try to payout to a rando key
|
2019-02-01 07:36:35 -08:00
|
|
|
let tx = BudgetTransaction::new_timestamp(
|
2018-09-17 13:36:31 -07:00
|
|
|
&from,
|
|
|
|
contract.pubkey(),
|
|
|
|
rando.pubkey(),
|
|
|
|
dt,
|
|
|
|
Hash::default(),
|
|
|
|
);
|
2018-09-24 16:00:55 -07:00
|
|
|
assert_eq!(
|
2018-09-28 16:16:35 -07:00
|
|
|
process_transaction(&tx, &mut accounts),
|
|
|
|
Err(BudgetError::DestinationMissing)
|
2018-09-24 16:00:55 -07:00
|
|
|
);
|
2018-09-17 13:36:31 -07:00
|
|
|
assert_eq!(accounts[from_account].tokens, 0);
|
|
|
|
assert_eq!(accounts[contract_account].tokens, 1);
|
|
|
|
assert_eq!(accounts[to_account].tokens, 0);
|
|
|
|
|
2019-03-03 13:17:51 -08:00
|
|
|
let budget_state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap();
|
|
|
|
assert!(budget_state.is_pending());
|
2018-09-17 13:36:31 -07:00
|
|
|
|
|
|
|
// Now, acknowledge the time in the condition occurred and
|
|
|
|
// that pubkey's funds are now available.
|
2019-02-01 07:36:35 -08:00
|
|
|
let tx = BudgetTransaction::new_timestamp(
|
2018-09-17 13:36:31 -07:00
|
|
|
&from,
|
|
|
|
contract.pubkey(),
|
|
|
|
to.pubkey(),
|
|
|
|
dt,
|
|
|
|
Hash::default(),
|
|
|
|
);
|
2018-09-28 16:16:35 -07:00
|
|
|
process_transaction(&tx, &mut accounts).unwrap();
|
2018-09-17 13:36:31 -07:00
|
|
|
assert_eq!(accounts[from_account].tokens, 0);
|
|
|
|
assert_eq!(accounts[contract_account].tokens, 0);
|
|
|
|
assert_eq!(accounts[to_account].tokens, 1);
|
|
|
|
|
2019-03-03 13:17:51 -08:00
|
|
|
let budget_state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap();
|
|
|
|
assert!(!budget_state.is_pending());
|
2018-09-17 13:36:31 -07:00
|
|
|
|
|
|
|
// try to replay the timestamp contract
|
2018-09-24 16:00:55 -07:00
|
|
|
assert_eq!(
|
2018-09-28 16:16:35 -07:00
|
|
|
process_transaction(&tx, &mut accounts),
|
|
|
|
Err(BudgetError::ContractNotPending)
|
2018-09-24 16:00:55 -07:00
|
|
|
);
|
2018-09-17 13:36:31 -07:00
|
|
|
assert_eq!(accounts[from_account].tokens, 0);
|
|
|
|
assert_eq!(accounts[contract_account].tokens, 0);
|
|
|
|
assert_eq!(accounts[to_account].tokens, 1);
|
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn test_cancel_transfer() {
|
|
|
|
let mut accounts = vec![
|
2018-11-23 12:45:34 -08:00
|
|
|
Account::new(1, 0, id()),
|
|
|
|
Account::new(0, 512, id()),
|
|
|
|
Account::new(0, 0, id()),
|
2018-09-17 13:36:31 -07:00
|
|
|
];
|
|
|
|
let from_account = 0;
|
|
|
|
let contract_account = 1;
|
|
|
|
let pay_account = 2;
|
|
|
|
let from = Keypair::new();
|
|
|
|
let contract = Keypair::new();
|
|
|
|
let to = Keypair::new();
|
|
|
|
let dt = Utc::now();
|
2019-02-01 07:36:35 -08:00
|
|
|
let tx = BudgetTransaction::new_on_date(
|
2018-09-17 13:36:31 -07:00
|
|
|
&from,
|
|
|
|
to.pubkey(),
|
|
|
|
contract.pubkey(),
|
|
|
|
dt,
|
2018-09-19 16:44:03 -07:00
|
|
|
from.pubkey(),
|
2018-09-22 16:51:21 -07:00
|
|
|
Some(from.pubkey()),
|
2018-09-17 13:36:31 -07:00
|
|
|
1,
|
|
|
|
Hash::default(),
|
|
|
|
);
|
2018-09-28 16:16:35 -07:00
|
|
|
process_transaction(&tx, &mut accounts).unwrap();
|
2018-09-17 13:36:31 -07:00
|
|
|
assert_eq!(accounts[from_account].tokens, 0);
|
|
|
|
assert_eq!(accounts[contract_account].tokens, 1);
|
2019-03-03 13:17:51 -08:00
|
|
|
let budget_state = BudgetState::deserialize(&accounts[contract_account].userdata).unwrap();
|
|
|
|
assert!(budget_state.is_pending());
|
2018-09-17 13:36:31 -07:00
|
|
|
|
|
|
|
// Attack! try to put the tokens into the wrong account with cancel
|
2018-09-18 18:45:44 -07:00
|
|
|
let tx =
|
2019-02-01 07:36:35 -08:00
|
|
|
BudgetTransaction::new_signature(&to, contract.pubkey(), to.pubkey(), Hash::default());
|
2018-09-17 13:36:31 -07:00
|
|
|
// unit test hack, the `from account` is passed instead of the `to` account to avoid
|
|
|
|
// creating more account vectors
|
2018-09-28 16:16:35 -07:00
|
|
|
process_transaction(&tx, &mut accounts).unwrap();
|
2018-09-17 13:36:31 -07:00
|
|
|
// nothing should be changed because apply witness didn't finalize a payment
|
|
|
|
assert_eq!(accounts[from_account].tokens, 0);
|
|
|
|
assert_eq!(accounts[contract_account].tokens, 1);
|
2019-02-07 11:14:10 -08:00
|
|
|
// this is the `to.pubkey()` account
|
2018-09-17 13:36:31 -07:00
|
|
|
assert_eq!(accounts[pay_account].tokens, 0);
|
|
|
|
|
|
|
|
// Now, cancel the transaction. from gets her funds back
|
2019-02-01 07:36:35 -08:00
|
|
|
let tx = BudgetTransaction::new_signature(
|
2018-09-17 13:36:31 -07:00
|
|
|
&from,
|
|
|
|
contract.pubkey(),
|
|
|
|
from.pubkey(),
|
|
|
|
Hash::default(),
|
|
|
|
);
|
2018-09-28 16:16:35 -07:00
|
|
|
process_transaction(&tx, &mut accounts).unwrap();
|
2019-02-07 11:14:10 -08:00
|
|
|
assert_eq!(accounts[from_account].tokens, 1);
|
2018-09-17 13:36:31 -07:00
|
|
|
assert_eq!(accounts[contract_account].tokens, 0);
|
2019-02-07 11:14:10 -08:00
|
|
|
assert_eq!(accounts[pay_account].tokens, 0);
|
2018-09-17 13:36:31 -07:00
|
|
|
|
2019-02-07 11:14:10 -08:00
|
|
|
// try to replay the cancel contract
|
2019-02-01 07:36:35 -08:00
|
|
|
let tx = BudgetTransaction::new_signature(
|
2018-09-17 13:36:31 -07:00
|
|
|
&from,
|
|
|
|
contract.pubkey(),
|
|
|
|
from.pubkey(),
|
|
|
|
Hash::default(),
|
|
|
|
);
|
2018-09-24 16:00:55 -07:00
|
|
|
assert_eq!(
|
2018-09-28 16:16:35 -07:00
|
|
|
process_transaction(&tx, &mut accounts),
|
|
|
|
Err(BudgetError::ContractNotPending)
|
2018-09-24 16:00:55 -07:00
|
|
|
);
|
2019-02-07 11:14:10 -08:00
|
|
|
assert_eq!(accounts[from_account].tokens, 1);
|
2018-09-17 13:36:31 -07:00
|
|
|
assert_eq!(accounts[contract_account].tokens, 0);
|
2019-02-07 11:14:10 -08:00
|
|
|
assert_eq!(accounts[pay_account].tokens, 0);
|
2018-09-17 13:36:31 -07:00
|
|
|
}
|
2018-09-17 21:09:11 -07:00
|
|
|
|
2018-09-20 09:38:37 -07:00
|
|
|
#[test]
|
|
|
|
fn test_userdata_too_small() {
|
|
|
|
let mut accounts = vec![
|
2018-11-23 12:45:34 -08:00
|
|
|
Account::new(1, 0, id()),
|
|
|
|
Account::new(1, 0, id()), // <== userdata is 0, which is not enough
|
|
|
|
Account::new(1, 0, id()),
|
2018-09-20 09:38:37 -07:00
|
|
|
];
|
|
|
|
let from = Keypair::new();
|
|
|
|
let contract = Keypair::new();
|
|
|
|
let to = Keypair::new();
|
2019-02-01 07:36:35 -08:00
|
|
|
let tx = BudgetTransaction::new_on_date(
|
2018-09-20 09:38:37 -07:00
|
|
|
&from,
|
|
|
|
to.pubkey(),
|
|
|
|
contract.pubkey(),
|
|
|
|
Utc::now(),
|
2018-09-21 18:51:42 -07:00
|
|
|
from.pubkey(),
|
2018-09-22 16:51:21 -07:00
|
|
|
None,
|
2018-09-20 09:38:37 -07:00
|
|
|
1,
|
|
|
|
Hash::default(),
|
|
|
|
);
|
|
|
|
|
2018-09-28 16:16:35 -07:00
|
|
|
assert!(process_transaction(&tx, &mut accounts).is_err());
|
2019-03-03 13:17:51 -08:00
|
|
|
assert!(BudgetState::deserialize(&accounts[1].userdata).is_err());
|
2018-09-20 09:38:37 -07:00
|
|
|
|
2019-02-01 07:36:35 -08:00
|
|
|
let tx = BudgetTransaction::new_timestamp(
|
2018-09-20 09:38:37 -07:00
|
|
|
&from,
|
|
|
|
contract.pubkey(),
|
|
|
|
to.pubkey(),
|
|
|
|
Utc::now(),
|
|
|
|
Hash::default(),
|
|
|
|
);
|
2018-09-28 16:16:35 -07:00
|
|
|
assert!(process_transaction(&tx, &mut accounts).is_err());
|
2019-03-03 13:17:51 -08:00
|
|
|
assert!(BudgetState::deserialize(&accounts[1].userdata).is_err());
|
2018-09-20 09:38:37 -07:00
|
|
|
|
|
|
|
// Success if there was no panic...
|
|
|
|
}
|
2018-09-17 13:36:31 -07:00
|
|
|
}
|