Add TransactionBuilder
This commit is contained in:
parent
4610706d9f
commit
404aa63147
|
@ -2290,6 +2290,7 @@ dependencies = [
|
|||
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ring 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -15,6 +15,7 @@ hex = "0.3.2"
|
|||
byteorder = "1.2.1"
|
||||
chrono = { version = "0.4.0", features = ["serde"] }
|
||||
generic-array = { version = "0.12.0", default-features = false, features = ["serde"] }
|
||||
itertools = "0.8.0"
|
||||
log = "0.4.2"
|
||||
ring = "0.13.2"
|
||||
sha2 = "0.8.0"
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use crate::budget_expr::BudgetExpr;
|
||||
use crate::budget_program;
|
||||
use crate::pubkey::Pubkey;
|
||||
use crate::transaction_builder::BuilderInstruction;
|
||||
use chrono::prelude::{DateTime, Utc};
|
||||
|
||||
/// A smart contract.
|
||||
|
@ -22,3 +25,13 @@ pub enum Instruction {
|
|||
/// signed by the containing transaction's `Pubkey`.
|
||||
ApplySignature,
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
pub fn new_budget(contract: Pubkey, expr: BudgetExpr) -> BuilderInstruction {
|
||||
BuilderInstruction::new(
|
||||
budget_program::id(),
|
||||
&Instruction::NewBudget(expr),
|
||||
vec![(contract, false)],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,11 @@ use crate::budget_expr::{BudgetExpr, Condition};
|
|||
use crate::budget_instruction::Instruction;
|
||||
use crate::budget_program;
|
||||
use crate::hash::Hash;
|
||||
use crate::payment_plan::Payment;
|
||||
use crate::pubkey::Pubkey;
|
||||
use crate::signature::{Keypair, KeypairUtil};
|
||||
use crate::system_instruction::SystemInstruction;
|
||||
use crate::system_program;
|
||||
use crate::transaction::{self, Transaction};
|
||||
use crate::transaction::Transaction;
|
||||
use crate::transaction_builder::TransactionBuilder;
|
||||
use bincode::deserialize;
|
||||
use chrono::prelude::*;
|
||||
|
||||
|
@ -25,31 +24,12 @@ impl BudgetTransaction {
|
|||
fee: u64,
|
||||
) -> Transaction {
|
||||
let contract = Keypair::new().pubkey();
|
||||
let keys = vec![from_keypair.pubkey(), contract];
|
||||
|
||||
let system_instruction = SystemInstruction::Move { tokens };
|
||||
|
||||
let payment = Payment {
|
||||
tokens: tokens - fee,
|
||||
to,
|
||||
};
|
||||
let budget_instruction = Instruction::NewBudget(BudgetExpr::Pay(payment));
|
||||
|
||||
let program_ids = vec![system_program::id(), budget_program::id()];
|
||||
|
||||
let instructions = vec![
|
||||
transaction::Instruction::new(0, &system_instruction, vec![0, 1]),
|
||||
transaction::Instruction::new(1, &budget_instruction, vec![1]),
|
||||
];
|
||||
|
||||
Transaction::new_with_instructions(
|
||||
&[from_keypair],
|
||||
&keys,
|
||||
last_id,
|
||||
fee,
|
||||
program_ids,
|
||||
instructions,
|
||||
)
|
||||
let from = from_keypair.pubkey();
|
||||
let payment = BudgetExpr::new_payment(tokens - fee, to);
|
||||
TransactionBuilder::new(fee)
|
||||
.push(SystemInstruction::new_move(from, contract, tokens))
|
||||
.push(Instruction::new_budget(contract, payment))
|
||||
.sign(&[from_keypair], last_id)
|
||||
}
|
||||
|
||||
/// Create and sign a new Transaction. Used for unit-testing.
|
||||
|
@ -230,23 +210,13 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_serialize_claim() {
|
||||
let expr = BudgetExpr::Pay(Payment {
|
||||
tokens: 0,
|
||||
to: Pubkey::default(),
|
||||
});
|
||||
let instruction = Instruction::NewBudget(expr);
|
||||
let instructions = vec![transaction::Instruction::new(0, &instruction, vec![])];
|
||||
let claim0 = Transaction {
|
||||
account_keys: vec![],
|
||||
last_id: Hash::default(),
|
||||
signatures: vec![],
|
||||
program_ids: vec![],
|
||||
instructions,
|
||||
fee: 0,
|
||||
};
|
||||
let buf = serialize(&claim0).unwrap();
|
||||
let claim1: Transaction = deserialize(&buf).unwrap();
|
||||
assert_eq!(claim1, claim0);
|
||||
let zero = Hash::default();
|
||||
let keypair0 = Keypair::new();
|
||||
let pubkey1 = Keypair::new().pubkey();
|
||||
let tx0 = BudgetTransaction::new_payment(&keypair0, pubkey1, 1, zero, 1);
|
||||
let buf = serialize(&tx0).unwrap();
|
||||
let tx1: Transaction = deserialize(&buf).unwrap();
|
||||
assert_eq!(tx1, tx0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -22,6 +22,7 @@ pub mod system_transaction;
|
|||
pub mod timing;
|
||||
pub mod token_program;
|
||||
pub mod transaction;
|
||||
pub mod transaction_builder;
|
||||
pub mod vote_program;
|
||||
pub mod vote_transaction;
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use crate::pubkey::Pubkey;
|
||||
use crate::system_program;
|
||||
use crate::transaction_builder::BuilderInstruction;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub enum SystemInstruction {
|
||||
|
@ -21,3 +23,13 @@ pub enum SystemInstruction {
|
|||
/// * Transaction::keys[1] - destination
|
||||
Move { tokens: u64 },
|
||||
}
|
||||
|
||||
impl SystemInstruction {
|
||||
pub fn new_move(from_id: Pubkey, to_id: Pubkey, tokens: u64) -> BuilderInstruction {
|
||||
BuilderInstruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::Move { tokens },
|
||||
vec![(from_id, true), (to_id, false)],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
//! A library for composing transactions.
|
||||
|
||||
use crate::hash::Hash;
|
||||
use crate::pubkey::Pubkey;
|
||||
use crate::signature::KeypairUtil;
|
||||
use crate::transaction::{Instruction, Transaction};
|
||||
use itertools::Itertools;
|
||||
|
||||
pub type BuilderInstruction = Instruction<Pubkey, (Pubkey, bool)>;
|
||||
|
||||
fn position(keys: &[Pubkey], key: Pubkey) -> u8 {
|
||||
keys.iter().position(|&k| k == key).unwrap() as u8
|
||||
}
|
||||
|
||||
fn create_indexed_instruction(
|
||||
ix: &Instruction<Pubkey, (Pubkey, bool)>,
|
||||
keys: &[Pubkey],
|
||||
program_ids: &[Pubkey],
|
||||
) -> Instruction<u8, u8> {
|
||||
let accounts: Vec<_> = ix
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|&(k, _)| position(keys, k))
|
||||
.collect();
|
||||
Instruction {
|
||||
program_ids_index: position(program_ids, ix.program_ids_index),
|
||||
userdata: ix.userdata.clone(),
|
||||
accounts,
|
||||
}
|
||||
}
|
||||
|
||||
/// A utility for constructing transactions
|
||||
#[derive(Default)]
|
||||
pub struct TransactionBuilder {
|
||||
fee: u64,
|
||||
instructions: Vec<BuilderInstruction>,
|
||||
}
|
||||
|
||||
impl TransactionBuilder {
|
||||
/// Create a new TransactionBuilder.
|
||||
pub fn new(fee: u64) -> Self {
|
||||
Self {
|
||||
fee,
|
||||
instructions: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an instruction.
|
||||
pub fn push(&mut self, instruction: BuilderInstruction) -> &mut Self {
|
||||
self.instructions.push(instruction);
|
||||
self
|
||||
}
|
||||
|
||||
/// Return pubkeys referenced by all instructions, with the ones needing signatures first.
|
||||
/// No duplicates and order is preserved.
|
||||
fn keys(&self) -> Vec<Pubkey> {
|
||||
let mut key_and_signed: Vec<_> = self
|
||||
.instructions
|
||||
.iter()
|
||||
.flat_map(|ix| ix.accounts.iter())
|
||||
.collect();
|
||||
key_and_signed.sort_by(|x, y| y.1.cmp(&x.1));
|
||||
key_and_signed.into_iter().map(|x| x.0).unique().collect()
|
||||
}
|
||||
|
||||
/// Return program ids referenced by all instructions. No duplicates and order is preserved.
|
||||
fn program_ids(&self) -> Vec<Pubkey> {
|
||||
self.instructions
|
||||
.iter()
|
||||
.map(|ix| ix.program_ids_index)
|
||||
.unique()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Return the instructions, but indexing lists of keys and program ids.
|
||||
fn instructions(&self, keys: &[Pubkey], program_ids: &[Pubkey]) -> Vec<Instruction<u8, u8>> {
|
||||
self.instructions
|
||||
.iter()
|
||||
.map(|ix| create_indexed_instruction(ix, keys, program_ids))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Return a signed transaction.
|
||||
pub fn sign<T: KeypairUtil>(&self, keypairs: &[&T], last_id: Hash) -> Transaction {
|
||||
let keys = self.keys();
|
||||
let program_ids = self.program_ids();
|
||||
let instructions = self.instructions(&keys, &program_ids);
|
||||
for (i, keypair) in keypairs.iter().enumerate() {
|
||||
assert_eq!(keypair.pubkey(), keys[i], "keypair-pubkey mismatch");
|
||||
}
|
||||
let unsigned_keys = &keys[keypairs.len()..];
|
||||
Transaction::new_with_instructions(
|
||||
keypairs,
|
||||
unsigned_keys,
|
||||
last_id,
|
||||
self.fee,
|
||||
program_ids,
|
||||
instructions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::signature::{Keypair, KeypairUtil};
|
||||
|
||||
#[test]
|
||||
fn test_transaction_builder_unique_program_ids() {
|
||||
let program_id0 = Pubkey::default();
|
||||
let program_ids = TransactionBuilder::default()
|
||||
.push(Instruction::new(program_id0, &0, vec![]))
|
||||
.push(Instruction::new(program_id0, &0, vec![]))
|
||||
.program_ids();
|
||||
assert_eq!(program_ids, vec![program_id0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_builder_unique_program_ids_not_adjacent() {
|
||||
let program_id0 = Pubkey::default();
|
||||
let program_id1 = Keypair::new().pubkey();
|
||||
let program_ids = TransactionBuilder::default()
|
||||
.push(Instruction::new(program_id0, &0, vec![]))
|
||||
.push(Instruction::new(program_id1, &0, vec![]))
|
||||
.push(Instruction::new(program_id0, &0, vec![]))
|
||||
.program_ids();
|
||||
assert_eq!(program_ids, vec![program_id0, program_id1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_builder_unique_program_ids_order_preserved() {
|
||||
let program_id0 = Keypair::new().pubkey();
|
||||
let program_id1 = Pubkey::default(); // Key less than program_id0
|
||||
let program_ids = TransactionBuilder::default()
|
||||
.push(Instruction::new(program_id0, &0, vec![]))
|
||||
.push(Instruction::new(program_id1, &0, vec![]))
|
||||
.push(Instruction::new(program_id0, &0, vec![]))
|
||||
.program_ids();
|
||||
assert_eq!(program_ids, vec![program_id0, program_id1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_builder_unique_keys_both_signed() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default();
|
||||
let keys = TransactionBuilder::default()
|
||||
.push(Instruction::new(program_id, &0, vec![(id0, true)]))
|
||||
.push(Instruction::new(program_id, &0, vec![(id0, true)]))
|
||||
.keys();
|
||||
assert_eq!(keys, vec![id0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_builder_unique_keys_one_signed() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default();
|
||||
let keys = TransactionBuilder::default()
|
||||
.push(Instruction::new(program_id, &0, vec![(id0, false)]))
|
||||
.push(Instruction::new(program_id, &0, vec![(id0, true)]))
|
||||
.keys();
|
||||
assert_eq!(keys, vec![id0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_builder_unique_keys_order_preserved() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Keypair::new().pubkey();
|
||||
let id1 = Pubkey::default(); // Key less than id0
|
||||
let keys = TransactionBuilder::default()
|
||||
.push(Instruction::new(program_id, &0, vec![(id0, false)]))
|
||||
.push(Instruction::new(program_id, &0, vec![(id1, false)]))
|
||||
.keys();
|
||||
assert_eq!(keys, vec![id0, id1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_builder_unique_keys_not_adjacent() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default();
|
||||
let id1 = Keypair::new().pubkey();
|
||||
let keys = TransactionBuilder::default()
|
||||
.push(Instruction::new(program_id, &0, vec![(id0, false)]))
|
||||
.push(Instruction::new(program_id, &0, vec![(id1, false)]))
|
||||
.push(Instruction::new(program_id, &0, vec![(id0, true)]))
|
||||
.keys();
|
||||
assert_eq!(keys, vec![id0, id1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_builder_signed_keys_first() {
|
||||
let program_id = Pubkey::default();
|
||||
let id0 = Pubkey::default();
|
||||
let id1 = Keypair::new().pubkey();
|
||||
let keys = TransactionBuilder::default()
|
||||
.push(Instruction::new(program_id, &0, vec![(id0, false)]))
|
||||
.push(Instruction::new(program_id, &0, vec![(id1, true)]))
|
||||
.keys();
|
||||
assert_eq!(keys, vec![id1, id0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_transaction_builder_missing_key() {
|
||||
let keypair = Keypair::new();
|
||||
TransactionBuilder::default().sign(&[&keypair], Hash::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_transaction_builder_wrong_key() {
|
||||
let program_id = Pubkey::default();
|
||||
let keypair0 = Keypair::new();
|
||||
let wrong_id = Pubkey::default();
|
||||
TransactionBuilder::default()
|
||||
.push(Instruction::new(program_id, &0, vec![(wrong_id, true)]))
|
||||
.sign(&[&keypair0], Hash::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_builder_correct_key() {
|
||||
let program_id = Pubkey::default();
|
||||
let keypair0 = Keypair::new();
|
||||
let id0 = keypair0.pubkey();
|
||||
let tx = TransactionBuilder::default()
|
||||
.push(Instruction::new(program_id, &0, vec![(id0, true)]))
|
||||
.sign(&[&keypair0], Hash::default());
|
||||
assert_eq!(tx.instructions[0], Instruction::new(0, &0, vec![0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_builder_kitchen_sink() {
|
||||
let program_id0 = Pubkey::default();
|
||||
let program_id1 = Keypair::new().pubkey();
|
||||
let id0 = Pubkey::default();
|
||||
let keypair1 = Keypair::new();
|
||||
let id1 = keypair1.pubkey();
|
||||
let tx = TransactionBuilder::default()
|
||||
.push(Instruction::new(program_id0, &0, vec![(id0, false)]))
|
||||
.push(Instruction::new(program_id1, &0, vec![(id1, true)]))
|
||||
.push(Instruction::new(program_id0, &0, vec![(id1, false)]))
|
||||
.sign(&[&keypair1], Hash::default());
|
||||
assert_eq!(tx.instructions[0], Instruction::new(0, &0, vec![1]));
|
||||
assert_eq!(tx.instructions[1], Instruction::new(1, &0, vec![0]));
|
||||
assert_eq!(tx.instructions[2], Instruction::new(0, &0, vec![0]));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue