Remove Budget from CLI (#11451)
* Remove support for Budget Also: * Make "pay" command a deprecated alias for the "transfer" command * chore: remove budget from web3.js * Drop Budget depedency from core Validators no longer ship with builtin Budget
This commit is contained in:
parent
7e25130529
commit
edadd5d6d5
|
@ -3428,7 +3428,6 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"solana-account-decoder",
|
"solana-account-decoder",
|
||||||
"solana-budget-program",
|
|
||||||
"solana-clap-utils",
|
"solana-clap-utils",
|
||||||
"solana-cli-config",
|
"solana-cli-config",
|
||||||
"solana-client",
|
"solana-client",
|
||||||
|
@ -3543,7 +3542,6 @@ dependencies = [
|
||||||
"solana-account-decoder",
|
"solana-account-decoder",
|
||||||
"solana-banks-server",
|
"solana-banks-server",
|
||||||
"solana-bpf-loader-program",
|
"solana-bpf-loader-program",
|
||||||
"solana-budget-program",
|
|
||||||
"solana-clap-utils",
|
"solana-clap-utils",
|
||||||
"solana-client",
|
"solana-client",
|
||||||
"solana-faucet",
|
"solana-faucet",
|
||||||
|
|
|
@ -28,7 +28,6 @@ serde = "1.0.112"
|
||||||
serde_derive = "1.0.103"
|
serde_derive = "1.0.103"
|
||||||
serde_json = "1.0.56"
|
serde_json = "1.0.56"
|
||||||
solana-account-decoder = { path = "../account-decoder", version = "1.4.0" }
|
solana-account-decoder = { path = "../account-decoder", version = "1.4.0" }
|
||||||
solana-budget-program = { path = "../programs/budget", version = "1.4.0" }
|
|
||||||
solana-clap-utils = { path = "../clap-utils", version = "1.4.0" }
|
solana-clap-utils = { path = "../clap-utils", version = "1.4.0" }
|
||||||
solana-cli-config = { path = "../cli-config", version = "1.4.0" }
|
solana-cli-config = { path = "../cli-config", version = "1.4.0" }
|
||||||
solana-client = { path = "../client", version = "1.4.0" }
|
solana-client = { path = "../client", version = "1.4.0" }
|
||||||
|
@ -49,7 +48,6 @@ url = "2.1.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-core = { path = "../core", version = "1.4.0" }
|
solana-core = { path = "../core", version = "1.4.0" }
|
||||||
solana-budget-program = { path = "../programs/budget", version = "1.4.0" }
|
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
938
cli/src/cli.rs
938
cli/src/cli.rs
File diff suppressed because it is too large
Load Diff
453
cli/tests/pay.rs
453
cli/tests/pay.rs
|
@ -1,453 +0,0 @@
|
||||||
use chrono::prelude::*;
|
|
||||||
use serde_json::Value;
|
|
||||||
use solana_cli::{
|
|
||||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig, PayCommand},
|
|
||||||
cli_output::OutputFormat,
|
|
||||||
nonce,
|
|
||||||
offline::{
|
|
||||||
blockhash_query::{self, BlockhashQuery},
|
|
||||||
parse_sign_only_reply_string,
|
|
||||||
},
|
|
||||||
spend_utils::SpendAmount,
|
|
||||||
test_utils::check_recent_balance,
|
|
||||||
};
|
|
||||||
use solana_client::rpc_client::RpcClient;
|
|
||||||
use solana_core::validator::TestValidator;
|
|
||||||
use solana_faucet::faucet::run_local_faucet;
|
|
||||||
use solana_sdk::{
|
|
||||||
commitment_config::CommitmentConfig,
|
|
||||||
nonce::State as NonceState,
|
|
||||||
pubkey::Pubkey,
|
|
||||||
signature::{Keypair, Signer},
|
|
||||||
};
|
|
||||||
use std::{fs::remove_dir_all, sync::mpsc::channel};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cli_timestamp_tx() {
|
|
||||||
let TestValidator {
|
|
||||||
server,
|
|
||||||
leader_data,
|
|
||||||
alice,
|
|
||||||
ledger_path,
|
|
||||||
..
|
|
||||||
} = TestValidator::run();
|
|
||||||
let bob_pubkey = Pubkey::new_rand();
|
|
||||||
|
|
||||||
let (sender, receiver) = channel();
|
|
||||||
run_local_faucet(alice, sender, None);
|
|
||||||
let faucet_addr = receiver.recv().unwrap();
|
|
||||||
|
|
||||||
let rpc_client = RpcClient::new_socket(leader_data.rpc);
|
|
||||||
let default_signer0 = Keypair::new();
|
|
||||||
let default_signer1 = Keypair::new();
|
|
||||||
|
|
||||||
let mut config_payer = CliConfig::recent_for_tests();
|
|
||||||
config_payer.json_rpc_url =
|
|
||||||
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
|
|
||||||
config_payer.signers = vec![&default_signer0];
|
|
||||||
|
|
||||||
let mut config_witness = CliConfig::recent_for_tests();
|
|
||||||
config_witness.json_rpc_url = config_payer.json_rpc_url.clone();
|
|
||||||
config_witness.signers = vec![&default_signer1];
|
|
||||||
|
|
||||||
assert_ne!(
|
|
||||||
config_payer.signers[0].pubkey(),
|
|
||||||
config_witness.signers[0].pubkey()
|
|
||||||
);
|
|
||||||
|
|
||||||
request_and_confirm_airdrop(
|
|
||||||
&rpc_client,
|
|
||||||
&faucet_addr,
|
|
||||||
&config_payer.signers[0].pubkey(),
|
|
||||||
50,
|
|
||||||
&config_witness,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
check_recent_balance(50, &rpc_client, &config_payer.signers[0].pubkey());
|
|
||||||
|
|
||||||
request_and_confirm_airdrop(
|
|
||||||
&rpc_client,
|
|
||||||
&faucet_addr,
|
|
||||||
&config_witness.signers[0].pubkey(),
|
|
||||||
1,
|
|
||||||
&config_witness,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Make transaction (from config_payer to bob_pubkey) requiring timestamp from config_witness
|
|
||||||
let date_string = "\"2018-09-19T17:30:59Z\"";
|
|
||||||
let dt: DateTime<Utc> = serde_json::from_str(&date_string).unwrap();
|
|
||||||
config_payer.command = CliCommand::Pay(PayCommand {
|
|
||||||
amount: SpendAmount::Some(10),
|
|
||||||
to: bob_pubkey,
|
|
||||||
timestamp: Some(dt),
|
|
||||||
timestamp_pubkey: Some(config_witness.signers[0].pubkey()),
|
|
||||||
..PayCommand::default()
|
|
||||||
});
|
|
||||||
let sig_response = process_command(&config_payer);
|
|
||||||
|
|
||||||
let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap();
|
|
||||||
let process_id_str = object.get("processId").unwrap().as_str().unwrap();
|
|
||||||
let process_id_vec = bs58::decode(process_id_str)
|
|
||||||
.into_vec()
|
|
||||||
.expect("base58-encoded public key");
|
|
||||||
let process_id = Pubkey::new(&process_id_vec);
|
|
||||||
|
|
||||||
check_recent_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance
|
|
||||||
check_recent_balance(10, &rpc_client, &process_id); // contract balance
|
|
||||||
check_recent_balance(0, &rpc_client, &bob_pubkey); // recipient balance
|
|
||||||
|
|
||||||
// Sign transaction by config_witness
|
|
||||||
config_witness.command = CliCommand::TimeElapsed(bob_pubkey, process_id, dt);
|
|
||||||
process_command(&config_witness).unwrap();
|
|
||||||
|
|
||||||
check_recent_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance
|
|
||||||
check_recent_balance(0, &rpc_client, &process_id); // contract balance
|
|
||||||
check_recent_balance(10, &rpc_client, &bob_pubkey); // recipient balance
|
|
||||||
|
|
||||||
server.close().unwrap();
|
|
||||||
remove_dir_all(ledger_path).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cli_witness_tx() {
|
|
||||||
let TestValidator {
|
|
||||||
server,
|
|
||||||
leader_data,
|
|
||||||
alice,
|
|
||||||
ledger_path,
|
|
||||||
..
|
|
||||||
} = TestValidator::run();
|
|
||||||
let bob_pubkey = Pubkey::new_rand();
|
|
||||||
|
|
||||||
let (sender, receiver) = channel();
|
|
||||||
run_local_faucet(alice, sender, None);
|
|
||||||
let faucet_addr = receiver.recv().unwrap();
|
|
||||||
|
|
||||||
let rpc_client = RpcClient::new_socket(leader_data.rpc);
|
|
||||||
let default_signer0 = Keypair::new();
|
|
||||||
let default_signer1 = Keypair::new();
|
|
||||||
|
|
||||||
let mut config_payer = CliConfig::recent_for_tests();
|
|
||||||
config_payer.json_rpc_url =
|
|
||||||
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
|
|
||||||
config_payer.signers = vec![&default_signer0];
|
|
||||||
|
|
||||||
let mut config_witness = CliConfig::recent_for_tests();
|
|
||||||
config_witness.json_rpc_url = config_payer.json_rpc_url.clone();
|
|
||||||
config_witness.signers = vec![&default_signer1];
|
|
||||||
|
|
||||||
assert_ne!(
|
|
||||||
config_payer.signers[0].pubkey(),
|
|
||||||
config_witness.signers[0].pubkey()
|
|
||||||
);
|
|
||||||
|
|
||||||
request_and_confirm_airdrop(
|
|
||||||
&rpc_client,
|
|
||||||
&faucet_addr,
|
|
||||||
&config_payer.signers[0].pubkey(),
|
|
||||||
50,
|
|
||||||
&config_witness,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
request_and_confirm_airdrop(
|
|
||||||
&rpc_client,
|
|
||||||
&faucet_addr,
|
|
||||||
&config_witness.signers[0].pubkey(),
|
|
||||||
1,
|
|
||||||
&config_witness,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness
|
|
||||||
config_payer.command = CliCommand::Pay(PayCommand {
|
|
||||||
amount: SpendAmount::Some(10),
|
|
||||||
to: bob_pubkey,
|
|
||||||
witnesses: Some(vec![config_witness.signers[0].pubkey()]),
|
|
||||||
..PayCommand::default()
|
|
||||||
});
|
|
||||||
let sig_response = process_command(&config_payer);
|
|
||||||
|
|
||||||
let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap();
|
|
||||||
let process_id_str = object.get("processId").unwrap().as_str().unwrap();
|
|
||||||
let process_id_vec = bs58::decode(process_id_str)
|
|
||||||
.into_vec()
|
|
||||||
.expect("base58-encoded public key");
|
|
||||||
let process_id = Pubkey::new(&process_id_vec);
|
|
||||||
|
|
||||||
check_recent_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance
|
|
||||||
check_recent_balance(10, &rpc_client, &process_id); // contract balance
|
|
||||||
check_recent_balance(0, &rpc_client, &bob_pubkey); // recipient balance
|
|
||||||
|
|
||||||
// Sign transaction by config_witness
|
|
||||||
config_witness.command = CliCommand::Witness(bob_pubkey, process_id);
|
|
||||||
process_command(&config_witness).unwrap();
|
|
||||||
|
|
||||||
check_recent_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance
|
|
||||||
check_recent_balance(0, &rpc_client, &process_id); // contract balance
|
|
||||||
check_recent_balance(10, &rpc_client, &bob_pubkey); // recipient balance
|
|
||||||
|
|
||||||
server.close().unwrap();
|
|
||||||
remove_dir_all(ledger_path).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cli_cancel_tx() {
|
|
||||||
let TestValidator {
|
|
||||||
server,
|
|
||||||
leader_data,
|
|
||||||
alice,
|
|
||||||
ledger_path,
|
|
||||||
..
|
|
||||||
} = TestValidator::run();
|
|
||||||
let bob_pubkey = Pubkey::new_rand();
|
|
||||||
|
|
||||||
let (sender, receiver) = channel();
|
|
||||||
run_local_faucet(alice, sender, None);
|
|
||||||
let faucet_addr = receiver.recv().unwrap();
|
|
||||||
|
|
||||||
let rpc_client = RpcClient::new_socket(leader_data.rpc);
|
|
||||||
let default_signer0 = Keypair::new();
|
|
||||||
let default_signer1 = Keypair::new();
|
|
||||||
|
|
||||||
let mut config_payer = CliConfig::recent_for_tests();
|
|
||||||
config_payer.json_rpc_url =
|
|
||||||
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
|
|
||||||
config_payer.signers = vec![&default_signer0];
|
|
||||||
|
|
||||||
let mut config_witness = CliConfig::recent_for_tests();
|
|
||||||
config_witness.json_rpc_url = config_payer.json_rpc_url.clone();
|
|
||||||
config_witness.signers = vec![&default_signer1];
|
|
||||||
|
|
||||||
assert_ne!(
|
|
||||||
config_payer.signers[0].pubkey(),
|
|
||||||
config_witness.signers[0].pubkey()
|
|
||||||
);
|
|
||||||
|
|
||||||
request_and_confirm_airdrop(
|
|
||||||
&rpc_client,
|
|
||||||
&faucet_addr,
|
|
||||||
&config_payer.signers[0].pubkey(),
|
|
||||||
50,
|
|
||||||
&config_witness,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness
|
|
||||||
config_payer.command = CliCommand::Pay(PayCommand {
|
|
||||||
amount: SpendAmount::Some(10),
|
|
||||||
to: bob_pubkey,
|
|
||||||
witnesses: Some(vec![config_witness.signers[0].pubkey()]),
|
|
||||||
cancelable: true,
|
|
||||||
..PayCommand::default()
|
|
||||||
});
|
|
||||||
let sig_response = process_command(&config_payer).unwrap();
|
|
||||||
|
|
||||||
let object: Value = serde_json::from_str(&sig_response).unwrap();
|
|
||||||
let process_id_str = object.get("processId").unwrap().as_str().unwrap();
|
|
||||||
let process_id_vec = bs58::decode(process_id_str)
|
|
||||||
.into_vec()
|
|
||||||
.expect("base58-encoded public key");
|
|
||||||
let process_id = Pubkey::new(&process_id_vec);
|
|
||||||
|
|
||||||
check_recent_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance
|
|
||||||
check_recent_balance(10, &rpc_client, &process_id); // contract balance
|
|
||||||
check_recent_balance(0, &rpc_client, &bob_pubkey); // recipient balance
|
|
||||||
|
|
||||||
// Sign transaction by config_witness
|
|
||||||
config_payer.command = CliCommand::Cancel(process_id);
|
|
||||||
process_command(&config_payer).unwrap();
|
|
||||||
|
|
||||||
check_recent_balance(50, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance
|
|
||||||
check_recent_balance(0, &rpc_client, &process_id); // contract balance
|
|
||||||
check_recent_balance(0, &rpc_client, &bob_pubkey); // recipient balance
|
|
||||||
|
|
||||||
server.close().unwrap();
|
|
||||||
remove_dir_all(ledger_path).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_offline_pay_tx() {
|
|
||||||
let TestValidator {
|
|
||||||
server,
|
|
||||||
leader_data,
|
|
||||||
alice,
|
|
||||||
ledger_path,
|
|
||||||
..
|
|
||||||
} = TestValidator::run();
|
|
||||||
let bob_pubkey = Pubkey::new_rand();
|
|
||||||
|
|
||||||
let (sender, receiver) = channel();
|
|
||||||
run_local_faucet(alice, sender, None);
|
|
||||||
let faucet_addr = receiver.recv().unwrap();
|
|
||||||
|
|
||||||
let rpc_client = RpcClient::new_socket(leader_data.rpc);
|
|
||||||
let default_signer = Keypair::new();
|
|
||||||
let default_offline_signer = Keypair::new();
|
|
||||||
|
|
||||||
let mut config_offline = CliConfig::recent_for_tests();
|
|
||||||
config_offline.json_rpc_url =
|
|
||||||
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
|
|
||||||
config_offline.signers = vec![&default_offline_signer];
|
|
||||||
let mut config_online = CliConfig::recent_for_tests();
|
|
||||||
config_online.json_rpc_url =
|
|
||||||
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
|
|
||||||
config_online.signers = vec![&default_signer];
|
|
||||||
assert_ne!(
|
|
||||||
config_offline.signers[0].pubkey(),
|
|
||||||
config_online.signers[0].pubkey()
|
|
||||||
);
|
|
||||||
|
|
||||||
request_and_confirm_airdrop(
|
|
||||||
&rpc_client,
|
|
||||||
&faucet_addr,
|
|
||||||
&config_offline.signers[0].pubkey(),
|
|
||||||
50,
|
|
||||||
&config_offline,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
request_and_confirm_airdrop(
|
|
||||||
&rpc_client,
|
|
||||||
&faucet_addr,
|
|
||||||
&config_online.signers[0].pubkey(),
|
|
||||||
50,
|
|
||||||
&config_offline,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
check_recent_balance(50, &rpc_client, &config_offline.signers[0].pubkey());
|
|
||||||
check_recent_balance(50, &rpc_client, &config_online.signers[0].pubkey());
|
|
||||||
|
|
||||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
|
||||||
config_offline.command = CliCommand::Pay(PayCommand {
|
|
||||||
amount: SpendAmount::Some(10),
|
|
||||||
to: bob_pubkey,
|
|
||||||
blockhash_query: BlockhashQuery::None(blockhash),
|
|
||||||
sign_only: true,
|
|
||||||
..PayCommand::default()
|
|
||||||
});
|
|
||||||
config_offline.output_format = OutputFormat::JsonCompact;
|
|
||||||
let sig_response = process_command(&config_offline).unwrap();
|
|
||||||
|
|
||||||
check_recent_balance(50, &rpc_client, &config_offline.signers[0].pubkey());
|
|
||||||
check_recent_balance(50, &rpc_client, &config_online.signers[0].pubkey());
|
|
||||||
check_recent_balance(0, &rpc_client, &bob_pubkey);
|
|
||||||
|
|
||||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
|
||||||
assert!(sign_only.has_all_signers());
|
|
||||||
let offline_presigner = sign_only
|
|
||||||
.presigner_of(&config_offline.signers[0].pubkey())
|
|
||||||
.unwrap();
|
|
||||||
let online_pubkey = config_online.signers[0].pubkey();
|
|
||||||
config_online.signers = vec![&offline_presigner];
|
|
||||||
config_online.command = CliCommand::Pay(PayCommand {
|
|
||||||
amount: SpendAmount::Some(10),
|
|
||||||
to: bob_pubkey,
|
|
||||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
|
||||||
..PayCommand::default()
|
|
||||||
});
|
|
||||||
process_command(&config_online).unwrap();
|
|
||||||
|
|
||||||
check_recent_balance(40, &rpc_client, &config_offline.signers[0].pubkey());
|
|
||||||
check_recent_balance(50, &rpc_client, &online_pubkey);
|
|
||||||
check_recent_balance(10, &rpc_client, &bob_pubkey);
|
|
||||||
|
|
||||||
server.close().unwrap();
|
|
||||||
remove_dir_all(ledger_path).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_nonced_pay_tx() {
|
|
||||||
solana_logger::setup();
|
|
||||||
|
|
||||||
let TestValidator {
|
|
||||||
server,
|
|
||||||
leader_data,
|
|
||||||
alice,
|
|
||||||
ledger_path,
|
|
||||||
..
|
|
||||||
} = TestValidator::run();
|
|
||||||
let (sender, receiver) = channel();
|
|
||||||
run_local_faucet(alice, sender, None);
|
|
||||||
let faucet_addr = receiver.recv().unwrap();
|
|
||||||
|
|
||||||
let rpc_client = RpcClient::new_socket(leader_data.rpc);
|
|
||||||
let default_signer = Keypair::new();
|
|
||||||
|
|
||||||
let mut config = CliConfig::recent_for_tests();
|
|
||||||
config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
|
|
||||||
config.signers = vec![&default_signer];
|
|
||||||
|
|
||||||
let minimum_nonce_balance = rpc_client
|
|
||||||
.get_minimum_balance_for_rent_exemption(NonceState::size())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
request_and_confirm_airdrop(
|
|
||||||
&rpc_client,
|
|
||||||
&faucet_addr,
|
|
||||||
&config.signers[0].pubkey(),
|
|
||||||
50 + minimum_nonce_balance,
|
|
||||||
&config,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
check_recent_balance(
|
|
||||||
50 + minimum_nonce_balance,
|
|
||||||
&rpc_client,
|
|
||||||
&config.signers[0].pubkey(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create nonce account
|
|
||||||
let nonce_account = Keypair::new();
|
|
||||||
config.command = CliCommand::CreateNonceAccount {
|
|
||||||
nonce_account: 1,
|
|
||||||
seed: None,
|
|
||||||
nonce_authority: Some(config.signers[0].pubkey()),
|
|
||||||
amount: SpendAmount::Some(minimum_nonce_balance),
|
|
||||||
};
|
|
||||||
config.signers.push(&nonce_account);
|
|
||||||
process_command(&config).unwrap();
|
|
||||||
|
|
||||||
check_recent_balance(50, &rpc_client, &config.signers[0].pubkey());
|
|
||||||
check_recent_balance(minimum_nonce_balance, &rpc_client, &nonce_account.pubkey());
|
|
||||||
|
|
||||||
// Fetch nonce hash
|
|
||||||
let nonce_hash = nonce::get_account_with_commitment(
|
|
||||||
&rpc_client,
|
|
||||||
&nonce_account.pubkey(),
|
|
||||||
CommitmentConfig::recent(),
|
|
||||||
)
|
|
||||||
.and_then(|ref a| nonce::data_from_account(a))
|
|
||||||
.unwrap()
|
|
||||||
.blockhash;
|
|
||||||
|
|
||||||
let bob_pubkey = Pubkey::new_rand();
|
|
||||||
config.signers = vec![&default_signer];
|
|
||||||
config.command = CliCommand::Pay(PayCommand {
|
|
||||||
amount: SpendAmount::Some(10),
|
|
||||||
to: bob_pubkey,
|
|
||||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
|
||||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
|
||||||
nonce_hash,
|
|
||||||
),
|
|
||||||
nonce_account: Some(nonce_account.pubkey()),
|
|
||||||
..PayCommand::default()
|
|
||||||
});
|
|
||||||
process_command(&config).expect("failed to process pay command");
|
|
||||||
|
|
||||||
check_recent_balance(40, &rpc_client, &config.signers[0].pubkey());
|
|
||||||
check_recent_balance(10, &rpc_client, &bob_pubkey);
|
|
||||||
|
|
||||||
// Verify that nonce has been used
|
|
||||||
let nonce_hash2 = nonce::get_account_with_commitment(
|
|
||||||
&rpc_client,
|
|
||||||
&nonce_account.pubkey(),
|
|
||||||
CommitmentConfig::recent(),
|
|
||||||
)
|
|
||||||
.and_then(|ref a| nonce::data_from_account(a))
|
|
||||||
.unwrap()
|
|
||||||
.blockhash;
|
|
||||||
assert_ne!(nonce_hash, nonce_hash2);
|
|
||||||
|
|
||||||
server.close().unwrap();
|
|
||||||
remove_dir_all(ledger_path).unwrap();
|
|
||||||
}
|
|
|
@ -46,7 +46,6 @@ serde_json = "1.0.56"
|
||||||
solana-account-decoder = { path = "../account-decoder", version = "1.4.0" }
|
solana-account-decoder = { path = "../account-decoder", version = "1.4.0" }
|
||||||
solana-banks-server = { path = "../banks-server", version = "1.4.0" }
|
solana-banks-server = { path = "../banks-server", version = "1.4.0" }
|
||||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.4.0" }
|
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.4.0" }
|
||||||
solana-budget-program = { path = "../programs/budget", version = "1.4.0" }
|
|
||||||
solana-clap-utils = { path = "../clap-utils", version = "1.4.0" }
|
solana-clap-utils = { path = "../clap-utils", version = "1.4.0" }
|
||||||
solana-client = { path = "../client", version = "1.4.0" }
|
solana-client = { path = "../client", version = "1.4.0" }
|
||||||
solana-faucet = { path = "../faucet", version = "1.4.0" }
|
solana-faucet = { path = "../faucet", version = "1.4.0" }
|
||||||
|
|
|
@ -74,9 +74,6 @@ pub mod window_service;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate solana_bpf_loader_program;
|
extern crate solana_bpf_loader_program;
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate solana_budget_program;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
|
|
|
@ -359,7 +359,6 @@ mod tests {
|
||||||
use jsonrpc_pubsub::{PubSubHandler, Session};
|
use jsonrpc_pubsub::{PubSubHandler, Session};
|
||||||
use serial_test_derive::serial;
|
use serial_test_derive::serial;
|
||||||
use solana_account_decoder::{parse_account_data::parse_account_data, UiAccountEncoding};
|
use solana_account_decoder::{parse_account_data::parse_account_data, UiAccountEncoding};
|
||||||
use solana_budget_program::{self, budget_instruction};
|
|
||||||
use solana_runtime::{
|
use solana_runtime::{
|
||||||
bank::Bank,
|
bank::Bank,
|
||||||
bank_forks::BankForks,
|
bank_forks::BankForks,
|
||||||
|
@ -377,6 +376,10 @@ mod tests {
|
||||||
system_instruction, system_program, system_transaction,
|
system_instruction, system_program, system_transaction,
|
||||||
transaction::{self, Transaction},
|
transaction::{self, Transaction},
|
||||||
};
|
};
|
||||||
|
use solana_stake_program::{
|
||||||
|
self, stake_instruction,
|
||||||
|
stake_state::{Authorized, Lockup, StakeAuthorize, StakeState},
|
||||||
|
};
|
||||||
use solana_vote_program::vote_transaction;
|
use solana_vote_program::vote_transaction;
|
||||||
use std::{
|
use std::{
|
||||||
sync::{atomic::AtomicBool, RwLock},
|
sync::{atomic::AtomicBool, RwLock},
|
||||||
|
@ -503,21 +506,16 @@ mod tests {
|
||||||
#[serial]
|
#[serial]
|
||||||
fn test_account_subscribe() {
|
fn test_account_subscribe() {
|
||||||
let GenesisConfigInfo {
|
let GenesisConfigInfo {
|
||||||
mut genesis_config,
|
genesis_config,
|
||||||
mint_keypair: alice,
|
mint_keypair: alice,
|
||||||
..
|
..
|
||||||
} = create_genesis_config(10_000);
|
} = create_genesis_config(10_000);
|
||||||
|
|
||||||
// This test depends on the budget program
|
let new_stake_authority = Pubkey::new_rand();
|
||||||
genesis_config
|
let stake_authority = Keypair::new();
|
||||||
.native_instruction_processors
|
let from = Keypair::new();
|
||||||
.push(solana_budget_program!());
|
let stake_account = Keypair::new();
|
||||||
|
let stake_program_id = solana_stake_program::id();
|
||||||
let bob_pubkey = Pubkey::new_rand();
|
|
||||||
let witness = Keypair::new();
|
|
||||||
let contract_funds = Keypair::new();
|
|
||||||
let contract_state = Keypair::new();
|
|
||||||
let budget_program_id = solana_budget_program::id();
|
|
||||||
let bank = Bank::new(&genesis_config);
|
let bank = Bank::new(&genesis_config);
|
||||||
let blockhash = bank.last_blockhash();
|
let blockhash = bank.last_blockhash();
|
||||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||||
|
@ -540,26 +538,26 @@ mod tests {
|
||||||
rpc.account_subscribe(
|
rpc.account_subscribe(
|
||||||
session,
|
session,
|
||||||
subscriber,
|
subscriber,
|
||||||
contract_state.pubkey().to_string(),
|
stake_account.pubkey().to_string(),
|
||||||
Some(RpcAccountInfoConfig {
|
Some(RpcAccountInfoConfig {
|
||||||
commitment: Some(CommitmentConfig::recent()),
|
commitment: Some(CommitmentConfig::recent()),
|
||||||
encoding: None,
|
encoding: None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let tx = system_transaction::transfer(&alice, &contract_funds.pubkey(), 51, blockhash);
|
let tx = system_transaction::transfer(&alice, &from.pubkey(), 51, blockhash);
|
||||||
process_transaction_and_notify(&bank_forks, &tx, &rpc.subscriptions, 1).unwrap();
|
process_transaction_and_notify(&bank_forks, &tx, &rpc.subscriptions, 1).unwrap();
|
||||||
|
|
||||||
let ixs = budget_instruction::when_signed(
|
let authorized = Authorized::auto(&stake_authority.pubkey());
|
||||||
&contract_funds.pubkey(),
|
let ixs = stake_instruction::create_account(
|
||||||
&bob_pubkey,
|
&from.pubkey(),
|
||||||
&contract_state.pubkey(),
|
&stake_account.pubkey(),
|
||||||
&witness.pubkey(),
|
&authorized,
|
||||||
None,
|
&Lockup::default(),
|
||||||
51,
|
51,
|
||||||
);
|
);
|
||||||
let message = Message::new(&ixs, Some(&contract_funds.pubkey()));
|
let message = Message::new(&ixs, Some(&from.pubkey()));
|
||||||
let tx = Transaction::new(&[&contract_funds, &contract_state], message, blockhash);
|
let tx = Transaction::new(&[&from, &stake_account], message, blockhash);
|
||||||
process_transaction_and_notify(&bank_forks, &tx, &rpc.subscriptions, 1).unwrap();
|
process_transaction_and_notify(&bank_forks, &tx, &rpc.subscriptions, 1).unwrap();
|
||||||
sleep(Duration::from_millis(200));
|
sleep(Duration::from_millis(200));
|
||||||
|
|
||||||
|
@ -569,7 +567,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get(1)
|
.get(1)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get_account(&contract_state.pubkey())
|
.get_account(&stake_account.pubkey())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.data;
|
.data;
|
||||||
let expected = json!({
|
let expected = json!({
|
||||||
|
@ -579,7 +577,7 @@ mod tests {
|
||||||
"result": {
|
"result": {
|
||||||
"context": { "slot": 1 },
|
"context": { "slot": 1 },
|
||||||
"value": {
|
"value": {
|
||||||
"owner": budget_program_id.to_string(),
|
"owner": stake_program_id.to_string(),
|
||||||
"lamports": 51,
|
"lamports": 51,
|
||||||
"data": bs58::encode(expected_data).into_string(),
|
"data": bs58::encode(expected_data).into_string(),
|
||||||
"executable": false,
|
"executable": false,
|
||||||
|
@ -593,27 +591,25 @@ mod tests {
|
||||||
let (response, _) = robust_poll_or_panic(receiver);
|
let (response, _) = robust_poll_or_panic(receiver);
|
||||||
assert_eq!(serde_json::to_string(&expected).unwrap(), response);
|
assert_eq!(serde_json::to_string(&expected).unwrap(), response);
|
||||||
|
|
||||||
let tx = system_transaction::transfer(&alice, &witness.pubkey(), 1, blockhash);
|
let tx = system_transaction::transfer(&alice, &stake_authority.pubkey(), 1, blockhash);
|
||||||
process_transaction_and_notify(&bank_forks, &tx, &rpc.subscriptions, 1).unwrap();
|
process_transaction_and_notify(&bank_forks, &tx, &rpc.subscriptions, 1).unwrap();
|
||||||
sleep(Duration::from_millis(200));
|
sleep(Duration::from_millis(200));
|
||||||
let ix = budget_instruction::apply_signature(
|
let ix = stake_instruction::authorize(
|
||||||
&witness.pubkey(),
|
&stake_account.pubkey(),
|
||||||
&contract_state.pubkey(),
|
&stake_authority.pubkey(),
|
||||||
&bob_pubkey,
|
&new_stake_authority,
|
||||||
|
StakeAuthorize::Staker,
|
||||||
);
|
);
|
||||||
let message = Message::new(&[ix], Some(&witness.pubkey()));
|
let message = Message::new(&[ix], Some(&stake_authority.pubkey()));
|
||||||
let tx = Transaction::new(&[&witness], message, blockhash);
|
let tx = Transaction::new(&[&stake_authority], message, blockhash);
|
||||||
process_transaction_and_notify(&bank_forks, &tx, &rpc.subscriptions, 1).unwrap();
|
process_transaction_and_notify(&bank_forks, &tx, &rpc.subscriptions, 1).unwrap();
|
||||||
sleep(Duration::from_millis(200));
|
sleep(Duration::from_millis(200));
|
||||||
|
|
||||||
|
let bank = bank_forks.read().unwrap()[1].clone();
|
||||||
|
let account = bank.get_account(&stake_account.pubkey()).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bank_forks
|
StakeState::authorized_from(&account).unwrap().staker,
|
||||||
.read()
|
new_stake_authority
|
||||||
.unwrap()
|
|
||||||
.get(1)
|
|
||||||
.unwrap()
|
|
||||||
.get_account(&contract_state.pubkey()),
|
|
||||||
None
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1044,7 +1044,7 @@ pub(crate) mod tests {
|
||||||
blockhash,
|
blockhash,
|
||||||
1,
|
1,
|
||||||
16,
|
16,
|
||||||
&solana_budget_program::id(),
|
&solana_stake_program::id(),
|
||||||
);
|
);
|
||||||
bank_forks
|
bank_forks
|
||||||
.write()
|
.write()
|
||||||
|
@ -1067,7 +1067,7 @@ pub(crate) mod tests {
|
||||||
"data": "1111111111111111",
|
"data": "1111111111111111",
|
||||||
"executable": false,
|
"executable": false,
|
||||||
"lamports": 1,
|
"lamports": 1,
|
||||||
"owner": "Budget1111111111111111111111111111111111111",
|
"owner": "Stake11111111111111111111111111111111111111",
|
||||||
"rentEpoch": 1,
|
"rentEpoch": 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1103,7 +1103,7 @@ pub(crate) mod tests {
|
||||||
blockhash,
|
blockhash,
|
||||||
1,
|
1,
|
||||||
16,
|
16,
|
||||||
&solana_budget_program::id(),
|
&solana_stake_program::id(),
|
||||||
);
|
);
|
||||||
bank_forks
|
bank_forks
|
||||||
.write()
|
.write()
|
||||||
|
@ -1123,7 +1123,7 @@ pub(crate) mod tests {
|
||||||
Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests())),
|
Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests())),
|
||||||
);
|
);
|
||||||
subscriptions.add_program_subscription(
|
subscriptions.add_program_subscription(
|
||||||
solana_budget_program::id(),
|
solana_stake_program::id(),
|
||||||
None,
|
None,
|
||||||
sub_id.clone(),
|
sub_id.clone(),
|
||||||
subscriber,
|
subscriber,
|
||||||
|
@ -1134,7 +1134,7 @@ pub(crate) mod tests {
|
||||||
.program_subscriptions
|
.program_subscriptions
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.contains_key(&solana_budget_program::id()));
|
.contains_key(&solana_stake_program::id()));
|
||||||
|
|
||||||
subscriptions.notify_subscribers(CommitmentSlots::default());
|
subscriptions.notify_subscribers(CommitmentSlots::default());
|
||||||
let (response, _) = robust_poll_or_panic(transport_receiver);
|
let (response, _) = robust_poll_or_panic(transport_receiver);
|
||||||
|
@ -1149,7 +1149,7 @@ pub(crate) mod tests {
|
||||||
"data": "1111111111111111",
|
"data": "1111111111111111",
|
||||||
"executable": false,
|
"executable": false,
|
||||||
"lamports": 1,
|
"lamports": 1,
|
||||||
"owner": "Budget1111111111111111111111111111111111111",
|
"owner": "Stake11111111111111111111111111111111111111",
|
||||||
"rentEpoch": 1,
|
"rentEpoch": 1,
|
||||||
},
|
},
|
||||||
"pubkey": alice.pubkey().to_string(),
|
"pubkey": alice.pubkey().to_string(),
|
||||||
|
@ -1166,7 +1166,7 @@ pub(crate) mod tests {
|
||||||
.program_subscriptions
|
.program_subscriptions
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.contains_key(&solana_budget_program::id()));
|
.contains_key(&solana_stake_program::id()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1528,7 +1528,7 @@ pub(crate) mod tests {
|
||||||
blockhash,
|
blockhash,
|
||||||
1,
|
1,
|
||||||
16,
|
16,
|
||||||
&solana_budget_program::id(),
|
&solana_stake_program::id(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add the transaction to the 1st bank and then freeze the bank
|
// Add the transaction to the 1st bank and then freeze the bank
|
||||||
|
@ -1562,7 +1562,7 @@ pub(crate) mod tests {
|
||||||
"data": "1111111111111111",
|
"data": "1111111111111111",
|
||||||
"executable": false,
|
"executable": false,
|
||||||
"lamports": 1,
|
"lamports": 1,
|
||||||
"owner": "Budget1111111111111111111111111111111111111",
|
"owner": "Stake11111111111111111111111111111111111111",
|
||||||
"rentEpoch": 1,
|
"rentEpoch": 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1595,7 +1595,7 @@ pub(crate) mod tests {
|
||||||
"data": "1111111111111111",
|
"data": "1111111111111111",
|
||||||
"executable": false,
|
"executable": false,
|
||||||
"lamports": 1,
|
"lamports": 1,
|
||||||
"owner": "Budget1111111111111111111111111111111111111",
|
"owner": "Stake11111111111111111111111111111111111111",
|
||||||
"rentEpoch": 1,
|
"rentEpoch": 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -868,9 +868,6 @@ impl TestValidator {
|
||||||
42,
|
42,
|
||||||
bootstrap_validator_lamports,
|
bootstrap_validator_lamports,
|
||||||
);
|
);
|
||||||
genesis_config
|
|
||||||
.native_instruction_processors
|
|
||||||
.push(solana_budget_program!());
|
|
||||||
genesis_config
|
genesis_config
|
||||||
.native_instruction_processors
|
.native_instruction_processors
|
||||||
.push(solana_bpf_loader_program!());
|
.push(solana_bpf_loader_program!());
|
||||||
|
|
|
@ -56,118 +56,4 @@ $ solana deploy <PATH>
|
||||||
<PROGRAM_ID>
|
<PROGRAM_ID>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Unconditional Immediate Transfer
|
|
||||||
|
|
||||||
```bash
|
|
||||||
// Command
|
|
||||||
$ solana pay <PUBKEY> 123
|
|
||||||
|
|
||||||
// Return
|
|
||||||
<TX_SIGNATURE>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Post-Dated Transfer
|
|
||||||
|
|
||||||
```bash
|
|
||||||
// Command
|
|
||||||
$ solana pay <PUBKEY> 123 \
|
|
||||||
--after 2018-12-24T23:59:00 --require-timestamp-from <PUBKEY>
|
|
||||||
|
|
||||||
// Return
|
|
||||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
|
||||||
```
|
|
||||||
|
|
||||||
_`require-timestamp-from` is optional. If not provided, the transaction will expect a timestamp signed by this wallet's private key_
|
|
||||||
|
|
||||||
### Authorized Transfer
|
|
||||||
|
|
||||||
A third party must send a signature to unlock the lamports.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
// Command
|
|
||||||
$ solana pay <PUBKEY> 123 \
|
|
||||||
--require-signature-from <PUBKEY>
|
|
||||||
|
|
||||||
// Return
|
|
||||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Post-Dated and Authorized Transfer
|
|
||||||
|
|
||||||
```bash
|
|
||||||
// Command
|
|
||||||
$ solana pay <PUBKEY> 123 \
|
|
||||||
--after 2018-12-24T23:59 --require-timestamp-from <PUBKEY> \
|
|
||||||
--require-signature-from <PUBKEY>
|
|
||||||
|
|
||||||
// Return
|
|
||||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multiple Witnesses
|
|
||||||
|
|
||||||
```bash
|
|
||||||
// Command
|
|
||||||
$ solana pay <PUBKEY> 123 \
|
|
||||||
--require-signature-from <PUBKEY> \
|
|
||||||
--require-signature-from <PUBKEY>
|
|
||||||
|
|
||||||
// Return
|
|
||||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cancelable Transfer
|
|
||||||
|
|
||||||
```bash
|
|
||||||
// Command
|
|
||||||
$ solana pay <PUBKEY> 123 \
|
|
||||||
--require-signature-from <PUBKEY> \
|
|
||||||
--cancelable
|
|
||||||
|
|
||||||
// Return
|
|
||||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cancel Transfer
|
|
||||||
|
|
||||||
```bash
|
|
||||||
// Command
|
|
||||||
$ solana cancel <PROCESS_ID>
|
|
||||||
|
|
||||||
// Return
|
|
||||||
<TX_SIGNATURE>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Send Signature
|
|
||||||
|
|
||||||
```bash
|
|
||||||
// Command
|
|
||||||
$ solana send-signature <PUBKEY> <PROCESS_ID>
|
|
||||||
|
|
||||||
// Return
|
|
||||||
<TX_SIGNATURE>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Indicate Elapsed Time
|
|
||||||
|
|
||||||
Use the current system time:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
// Command
|
|
||||||
$ solana send-timestamp <PUBKEY> <PROCESS_ID>
|
|
||||||
|
|
||||||
// Return
|
|
||||||
<TX_SIGNATURE>
|
|
||||||
```
|
|
||||||
|
|
||||||
Or specify some other arbitrary timestamp:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
// Command
|
|
||||||
$ solana send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
|
|
||||||
|
|
||||||
// Return
|
|
||||||
<TX_SIGNATURE>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
/* eslint-disable import/no-commonjs */
|
|
||||||
|
|
||||||
/*
|
|
||||||
Common code for the budget program examples
|
|
||||||
*/
|
|
||||||
|
|
||||||
function getTransactionFee(connection) {
|
|
||||||
return connection.getRecentBlockhash().then(response => {
|
|
||||||
return response.feeCalculator;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function showBalance(connection, account1, account2, contractState) {
|
|
||||||
console.log(`\n== Account State`);
|
|
||||||
return Promise.all([
|
|
||||||
connection.getBalance(account1.publicKey),
|
|
||||||
connection.getBalance(account2.publicKey),
|
|
||||||
connection.getBalance(contractState.publicKey),
|
|
||||||
]).then(([fromBalance, toBalance, contractStateBalance]) => {
|
|
||||||
console.log(
|
|
||||||
`Account1: ${account1.publicKey} has a balance of ${fromBalance}`,
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`Account2: ${account2.publicKey} has a balance of ${toBalance}`,
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`Contract State: ${contractState.publicKey} has a balance of ${contractStateBalance}`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmTransaction(connection, signature) {
|
|
||||||
console.log('Confirming transaction:', signature);
|
|
||||||
return connection.getSignatureStatus(signature).then(confirmation => {
|
|
||||||
if (confirmation && 'Ok' in confirmation) {
|
|
||||||
console.log('Transaction confirmed');
|
|
||||||
} else if (confirmation) {
|
|
||||||
throw new Error(
|
|
||||||
`Transaction was not confirmed (${JSON.stringify(confirmation.Err)})`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw new Error(`Transaction was not confirmed (${confirmation})`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function airDrop(connection, account, feeCalculator) {
|
|
||||||
const airdrop = 100 + 5 * feeCalculator.targetLamportsPerSignature;
|
|
||||||
console.log(`\n== Requesting airdrop of ${airdrop} to ${account.publicKey}`);
|
|
||||||
return connection
|
|
||||||
.requestAirdrop(account.publicKey, airdrop)
|
|
||||||
.then(signature => confirmTransaction(connection, signature));
|
|
||||||
}
|
|
||||||
|
|
||||||
function sleep(millis) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
setTimeout(resolve, millis);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
airDrop,
|
|
||||||
confirmTransaction,
|
|
||||||
getTransactionFee,
|
|
||||||
showBalance,
|
|
||||||
sleep,
|
|
||||||
};
|
|
|
@ -1,76 +0,0 @@
|
||||||
/* eslint-disable import/no-commonjs */
|
|
||||||
|
|
||||||
/*
|
|
||||||
Example of using the Budget program to perform a time-lock payment of 50
|
|
||||||
lamports from account1 to account2.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const common = require('./budget-common');
|
|
||||||
const solanaWeb3 = require('..');
|
|
||||||
//const solanaWeb3 = require('@solana/web3.js');
|
|
||||||
|
|
||||||
const account1 = new solanaWeb3.Account();
|
|
||||||
const account2 = new solanaWeb3.Account();
|
|
||||||
const contractState = new solanaWeb3.Account();
|
|
||||||
|
|
||||||
let url;
|
|
||||||
url = 'http://localhost:8899';
|
|
||||||
const connection = new solanaWeb3.Connection(url, 'recent');
|
|
||||||
const getTransactionFee = () => common.getTransactionFee(connection);
|
|
||||||
const showBalance = () =>
|
|
||||||
common.showBalance(connection, account1, account2, contractState);
|
|
||||||
const confirmTransaction = signature =>
|
|
||||||
common.confirmTransaction(connection, signature);
|
|
||||||
const airDrop = feeCalculator =>
|
|
||||||
common.airDrop(connection, account1, feeCalculator);
|
|
||||||
|
|
||||||
getTransactionFee().then(feeCalculator => {
|
|
||||||
airDrop(feeCalculator)
|
|
||||||
.then(showBalance)
|
|
||||||
.then(() => {
|
|
||||||
console.log(`\n== Initializing contract`);
|
|
||||||
const transaction = solanaWeb3.BudgetProgram.pay(
|
|
||||||
account1.publicKey,
|
|
||||||
contractState.publicKey,
|
|
||||||
account2.publicKey,
|
|
||||||
50,
|
|
||||||
solanaWeb3.BudgetProgram.timestampCondition(
|
|
||||||
account1.publicKey,
|
|
||||||
new Date('2050'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return solanaWeb3.sendAndConfirmTransaction(
|
|
||||||
connection,
|
|
||||||
transaction,
|
|
||||||
account1,
|
|
||||||
contractState,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then(confirmTransaction)
|
|
||||||
.then(showBalance)
|
|
||||||
.then(() => {
|
|
||||||
console.log(`\n== Witness contract`);
|
|
||||||
const transaction = solanaWeb3.BudgetProgram.applyTimestamp(
|
|
||||||
account1.publicKey,
|
|
||||||
contractState.publicKey,
|
|
||||||
account2.publicKey,
|
|
||||||
new Date('2050'),
|
|
||||||
);
|
|
||||||
return solanaWeb3.sendAndConfirmTransaction(
|
|
||||||
connection,
|
|
||||||
transaction,
|
|
||||||
account1,
|
|
||||||
contractState,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then(confirmTransaction)
|
|
||||||
.then(showBalance)
|
|
||||||
|
|
||||||
.then(() => {
|
|
||||||
console.log('\nDone');
|
|
||||||
})
|
|
||||||
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,119 +0,0 @@
|
||||||
/* eslint-disable import/no-commonjs */
|
|
||||||
|
|
||||||
/*
|
|
||||||
Example of using the Budget program to perform a payment authorized by two parties
|
|
||||||
*/
|
|
||||||
|
|
||||||
const common = require('./budget-common');
|
|
||||||
const solanaWeb3 = require('..');
|
|
||||||
//const solanaWeb3 = require('@solana/web3.js');
|
|
||||||
|
|
||||||
const account1 = new solanaWeb3.Account();
|
|
||||||
const account2 = new solanaWeb3.Account();
|
|
||||||
const contractState = new solanaWeb3.Account();
|
|
||||||
|
|
||||||
const approver1 = new solanaWeb3.Account();
|
|
||||||
const approver2 = new solanaWeb3.Account();
|
|
||||||
|
|
||||||
let url;
|
|
||||||
url = 'http://localhost:8899';
|
|
||||||
//url = 'http://devnet.solana.com';
|
|
||||||
const connection = new solanaWeb3.Connection(url, 'recent');
|
|
||||||
const getTransactionFee = () => common.getTransactionFee(connection);
|
|
||||||
const showBalance = () =>
|
|
||||||
common.showBalance(connection, account1, account2, contractState);
|
|
||||||
const confirmTransaction = signature =>
|
|
||||||
common.confirmTransaction(connection, signature);
|
|
||||||
const airDrop = feeCalculator =>
|
|
||||||
common.airDrop(connection, account1, feeCalculator);
|
|
||||||
|
|
||||||
getTransactionFee().then(feeCalculator => {
|
|
||||||
airDrop(feeCalculator)
|
|
||||||
.then(() => {
|
|
||||||
console.log(`\n== Move 1 lamport to approver1`);
|
|
||||||
const transaction = solanaWeb3.SystemProgram.transfer(
|
|
||||||
account1.publicKey,
|
|
||||||
approver1.publicKey,
|
|
||||||
1 + feeCalculator.lamportsPerSignature,
|
|
||||||
);
|
|
||||||
return solanaWeb3.sendAndConfirmTransaction(
|
|
||||||
connection,
|
|
||||||
transaction,
|
|
||||||
account1,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then(confirmTransaction)
|
|
||||||
.then(getTransactionFee)
|
|
||||||
.then(() => {
|
|
||||||
console.log(`\n== Move 1 lamport to approver2`);
|
|
||||||
const transaction = solanaWeb3.SystemProgram.transfer(
|
|
||||||
account1.publicKey,
|
|
||||||
approver2.publicKey,
|
|
||||||
1 + feeCalculator.lamportsPerSignature,
|
|
||||||
);
|
|
||||||
return solanaWeb3.sendAndConfirmTransaction(
|
|
||||||
connection,
|
|
||||||
transaction,
|
|
||||||
account1,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then(confirmTransaction)
|
|
||||||
.then(showBalance)
|
|
||||||
.then(() => {
|
|
||||||
console.log(`\n== Initializing contract`);
|
|
||||||
const transaction = solanaWeb3.BudgetProgram.payOnBoth(
|
|
||||||
account1.publicKey,
|
|
||||||
contractState.publicKey,
|
|
||||||
account2.publicKey,
|
|
||||||
50,
|
|
||||||
solanaWeb3.BudgetProgram.signatureCondition(approver1.publicKey),
|
|
||||||
solanaWeb3.BudgetProgram.signatureCondition(approver2.publicKey),
|
|
||||||
);
|
|
||||||
return solanaWeb3.sendAndConfirmTransaction(
|
|
||||||
connection,
|
|
||||||
transaction,
|
|
||||||
account1,
|
|
||||||
contractState,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then(confirmTransaction)
|
|
||||||
.then(showBalance)
|
|
||||||
.then(() => {
|
|
||||||
console.log(`\n== Apply approver 1`);
|
|
||||||
const transaction = solanaWeb3.BudgetProgram.applySignature(
|
|
||||||
approver1.publicKey,
|
|
||||||
contractState.publicKey,
|
|
||||||
account2.publicKey,
|
|
||||||
);
|
|
||||||
return solanaWeb3.sendAndConfirmTransaction(
|
|
||||||
connection,
|
|
||||||
transaction,
|
|
||||||
approver1,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then(confirmTransaction)
|
|
||||||
.then(showBalance)
|
|
||||||
.then(() => {
|
|
||||||
console.log(`\n== Apply approver 2`);
|
|
||||||
const transaction = solanaWeb3.BudgetProgram.applySignature(
|
|
||||||
approver2.publicKey,
|
|
||||||
contractState.publicKey,
|
|
||||||
account2.publicKey,
|
|
||||||
);
|
|
||||||
return solanaWeb3.sendAndConfirmTransaction(
|
|
||||||
connection,
|
|
||||||
transaction,
|
|
||||||
approver2,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then(confirmTransaction)
|
|
||||||
.then(showBalance)
|
|
||||||
|
|
||||||
.then(() => {
|
|
||||||
console.log('\nDone');
|
|
||||||
})
|
|
||||||
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -32,10 +32,6 @@ declare module '@solana/web3.js' {
|
||||||
lamportsPerSignature: number;
|
lamportsPerSignature: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
// === src/budget-program.js ===
|
|
||||||
|
|
||||||
/* TODO */
|
|
||||||
|
|
||||||
// === src/connection.js ===
|
// === src/connection.js ===
|
||||||
export type Context = {
|
export type Context = {
|
||||||
slot: number;
|
slot: number;
|
||||||
|
|
|
@ -54,9 +54,6 @@ declare module '@solana/web3.js' {
|
||||||
lamportsPerSignature: number,
|
lamportsPerSignature: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
// === src/budget-program.js ===
|
|
||||||
/* TODO */
|
|
||||||
|
|
||||||
// === src/connection.js ===
|
// === src/connection.js ===
|
||||||
declare export type Context = {
|
declare export type Context = {
|
||||||
slot: number,
|
slot: number,
|
||||||
|
|
|
@ -1,390 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import * as BufferLayout from 'buffer-layout';
|
|
||||||
|
|
||||||
import {Transaction} from './transaction';
|
|
||||||
import {PublicKey} from './publickey';
|
|
||||||
import {SystemProgram} from './system-program';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a condition that is met by executing a `applySignature()`
|
|
||||||
* transaction
|
|
||||||
*
|
|
||||||
* @typedef {Object} SignatureCondition
|
|
||||||
* @property {string} type Must equal the string 'timestamp'
|
|
||||||
* @property {PublicKey} from Public key from which `applySignature()` will be accepted from
|
|
||||||
*/
|
|
||||||
export type SignatureCondition = {
|
|
||||||
type: 'signature',
|
|
||||||
from: PublicKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a condition that is met by executing a `applyTimestamp()`
|
|
||||||
* transaction
|
|
||||||
*
|
|
||||||
* @typedef {Object} TimestampCondition
|
|
||||||
* @property {string} type Must equal the string 'timestamp'
|
|
||||||
* @property {PublicKey} from Public key from which `applyTimestamp()` will be accepted from
|
|
||||||
* @property {Date} when The timestamp that was observed
|
|
||||||
*/
|
|
||||||
export type TimestampCondition = {
|
|
||||||
type: 'timestamp',
|
|
||||||
from: PublicKey,
|
|
||||||
when: Date,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a payment to a given public key
|
|
||||||
*
|
|
||||||
* @typedef {Object} Payment
|
|
||||||
* @property {number} amount Number of lamports
|
|
||||||
* @property {PublicKey} to Public key of the recipient
|
|
||||||
*/
|
|
||||||
export type Payment = {
|
|
||||||
amount: number,
|
|
||||||
to: PublicKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A condition that can unlock a payment
|
|
||||||
*
|
|
||||||
* @typedef {SignatureCondition|TimestampCondition} BudgetCondition
|
|
||||||
*/
|
|
||||||
export type BudgetCondition = SignatureCondition | TimestampCondition;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function serializePayment(payment: Payment): Buffer {
|
|
||||||
const toData = payment.to.toBuffer();
|
|
||||||
const data = Buffer.alloc(8 + toData.length);
|
|
||||||
data.writeUInt32LE(payment.amount, 0);
|
|
||||||
toData.copy(data, 8);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function serializeDate(when: Date): Buffer {
|
|
||||||
const data = Buffer.alloc(8 + 20);
|
|
||||||
data.writeUInt32LE(20, 0); // size of timestamp as u64
|
|
||||||
|
|
||||||
function iso(date) {
|
|
||||||
function pad(number) {
|
|
||||||
if (number < 10) {
|
|
||||||
return '0' + number;
|
|
||||||
}
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
date.getUTCFullYear() +
|
|
||||||
'-' +
|
|
||||||
pad(date.getUTCMonth() + 1) +
|
|
||||||
'-' +
|
|
||||||
pad(date.getUTCDate()) +
|
|
||||||
'T' +
|
|
||||||
pad(date.getUTCHours()) +
|
|
||||||
':' +
|
|
||||||
pad(date.getUTCMinutes()) +
|
|
||||||
':' +
|
|
||||||
pad(date.getUTCSeconds()) +
|
|
||||||
'Z'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
data.write(iso(when), 8);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function serializeCondition(condition: BudgetCondition) {
|
|
||||||
switch (condition.type) {
|
|
||||||
case 'timestamp': {
|
|
||||||
const date = serializeDate(condition.when);
|
|
||||||
const from = condition.from.toBuffer();
|
|
||||||
|
|
||||||
const data = Buffer.alloc(4 + date.length + from.length);
|
|
||||||
data.writeUInt32LE(0, 0); // Condition enum = Timestamp
|
|
||||||
date.copy(data, 4);
|
|
||||||
from.copy(data, 4 + date.length);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
case 'signature': {
|
|
||||||
const from = condition.from.toBuffer();
|
|
||||||
const data = Buffer.alloc(4 + from.length);
|
|
||||||
data.writeUInt32LE(1, 0); // Condition enum = Signature
|
|
||||||
from.copy(data, 4);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown condition type: ${condition.type}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory class for transactions to interact with the Budget program
|
|
||||||
*/
|
|
||||||
export class BudgetProgram {
|
|
||||||
/**
|
|
||||||
* Public key that identifies the Budget program
|
|
||||||
*/
|
|
||||||
static get programId(): PublicKey {
|
|
||||||
return new PublicKey('Budget1111111111111111111111111111111111111');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The amount of space this program requires
|
|
||||||
*/
|
|
||||||
static get space(): number {
|
|
||||||
return 128;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a timestamp condition
|
|
||||||
*/
|
|
||||||
static timestampCondition(from: PublicKey, when: Date): TimestampCondition {
|
|
||||||
return {
|
|
||||||
type: 'timestamp',
|
|
||||||
from,
|
|
||||||
when,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a signature condition
|
|
||||||
*/
|
|
||||||
static signatureCondition(from: PublicKey): SignatureCondition {
|
|
||||||
return {
|
|
||||||
type: 'signature',
|
|
||||||
from,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a transaction that transfers lamports once any of the conditions are met
|
|
||||||
*/
|
|
||||||
static pay(
|
|
||||||
from: PublicKey,
|
|
||||||
program: PublicKey,
|
|
||||||
to: PublicKey,
|
|
||||||
amount: number,
|
|
||||||
...conditions: Array<BudgetCondition>
|
|
||||||
): Transaction {
|
|
||||||
const data = Buffer.alloc(1024);
|
|
||||||
let pos = 0;
|
|
||||||
data.writeUInt32LE(0, pos); // NewBudget instruction
|
|
||||||
pos += 4;
|
|
||||||
|
|
||||||
switch (conditions.length) {
|
|
||||||
case 0: {
|
|
||||||
data.writeUInt32LE(0, pos); // BudgetExpr enum = Pay
|
|
||||||
pos += 4;
|
|
||||||
|
|
||||||
{
|
|
||||||
const payment = serializePayment({amount, to});
|
|
||||||
payment.copy(data, pos);
|
|
||||||
pos += payment.length;
|
|
||||||
}
|
|
||||||
const trimmedData = data.slice(0, pos);
|
|
||||||
|
|
||||||
const transaction = SystemProgram.createAccount({
|
|
||||||
fromPubkey: from,
|
|
||||||
newAccountPubkey: program,
|
|
||||||
lamports: amount,
|
|
||||||
space: trimmedData.length,
|
|
||||||
programId: this.programId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return transaction.add({
|
|
||||||
keys: [
|
|
||||||
{pubkey: to, isSigner: false, isWritable: true},
|
|
||||||
{pubkey: program, isSigner: false, isWritable: true},
|
|
||||||
],
|
|
||||||
programId: this.programId,
|
|
||||||
data: trimmedData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case 1: {
|
|
||||||
data.writeUInt32LE(1, pos); // BudgetExpr enum = After
|
|
||||||
pos += 4;
|
|
||||||
{
|
|
||||||
const condition = conditions[0];
|
|
||||||
|
|
||||||
const conditionData = serializeCondition(condition);
|
|
||||||
conditionData.copy(data, pos);
|
|
||||||
pos += conditionData.length;
|
|
||||||
|
|
||||||
data.writeUInt32LE(0, pos); // BudgetExpr enum = Pay
|
|
||||||
pos += 4;
|
|
||||||
|
|
||||||
const paymentData = serializePayment({amount, to});
|
|
||||||
paymentData.copy(data, pos);
|
|
||||||
pos += paymentData.length;
|
|
||||||
}
|
|
||||||
const trimmedData = data.slice(0, pos);
|
|
||||||
|
|
||||||
const transaction = SystemProgram.createAccount({
|
|
||||||
fromPubkey: from,
|
|
||||||
newAccountPubkey: program,
|
|
||||||
lamports: amount,
|
|
||||||
space: trimmedData.length,
|
|
||||||
programId: this.programId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return transaction.add({
|
|
||||||
keys: [{pubkey: program, isSigner: false, isWritable: true}],
|
|
||||||
programId: this.programId,
|
|
||||||
data: trimmedData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
case 2: {
|
|
||||||
data.writeUInt32LE(2, pos); // BudgetExpr enum = Or
|
|
||||||
pos += 4;
|
|
||||||
|
|
||||||
for (let condition of conditions) {
|
|
||||||
const conditionData = serializeCondition(condition);
|
|
||||||
conditionData.copy(data, pos);
|
|
||||||
pos += conditionData.length;
|
|
||||||
|
|
||||||
data.writeUInt32LE(0, pos); // BudgetExpr enum = Pay
|
|
||||||
pos += 4;
|
|
||||||
|
|
||||||
const paymentData = serializePayment({amount, to});
|
|
||||||
paymentData.copy(data, pos);
|
|
||||||
pos += paymentData.length;
|
|
||||||
}
|
|
||||||
const trimmedData = data.slice(0, pos);
|
|
||||||
|
|
||||||
const transaction = SystemProgram.createAccount({
|
|
||||||
fromPubkey: from,
|
|
||||||
newAccountPubkey: program,
|
|
||||||
lamports: amount,
|
|
||||||
space: trimmedData.length,
|
|
||||||
programId: this.programId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return transaction.add({
|
|
||||||
keys: [{pubkey: program, isSigner: false, isWritable: true}],
|
|
||||||
programId: this.programId,
|
|
||||||
data: trimmedData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
`A maximum of two conditions are supported: ${conditions.length} provided`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a transaction that transfers lamports once both conditions are met
|
|
||||||
*/
|
|
||||||
static payOnBoth(
|
|
||||||
from: PublicKey,
|
|
||||||
program: PublicKey,
|
|
||||||
to: PublicKey,
|
|
||||||
amount: number,
|
|
||||||
condition1: BudgetCondition,
|
|
||||||
condition2: BudgetCondition,
|
|
||||||
): Transaction {
|
|
||||||
const data = Buffer.alloc(1024);
|
|
||||||
let pos = 0;
|
|
||||||
data.writeUInt32LE(0, pos); // NewBudget instruction
|
|
||||||
pos += 4;
|
|
||||||
|
|
||||||
data.writeUInt32LE(3, pos); // BudgetExpr enum = And
|
|
||||||
pos += 4;
|
|
||||||
|
|
||||||
for (let condition of [condition1, condition2]) {
|
|
||||||
const conditionData = serializeCondition(condition);
|
|
||||||
conditionData.copy(data, pos);
|
|
||||||
pos += conditionData.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.writeUInt32LE(0, pos); // BudgetExpr enum = Pay
|
|
||||||
pos += 4;
|
|
||||||
|
|
||||||
const paymentData = serializePayment({amount, to});
|
|
||||||
paymentData.copy(data, pos);
|
|
||||||
pos += paymentData.length;
|
|
||||||
|
|
||||||
const trimmedData = data.slice(0, pos);
|
|
||||||
|
|
||||||
const transaction = SystemProgram.createAccount({
|
|
||||||
fromPubkey: from,
|
|
||||||
newAccountPubkey: program,
|
|
||||||
lamports: amount,
|
|
||||||
space: trimmedData.length,
|
|
||||||
programId: this.programId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return transaction.add({
|
|
||||||
keys: [{pubkey: program, isSigner: false, isWritable: true}],
|
|
||||||
programId: this.programId,
|
|
||||||
data: trimmedData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a transaction that applies a timestamp, which could enable a
|
|
||||||
* pending payment to proceed.
|
|
||||||
*/
|
|
||||||
static applyTimestamp(
|
|
||||||
from: PublicKey,
|
|
||||||
program: PublicKey,
|
|
||||||
to: PublicKey,
|
|
||||||
when: Date,
|
|
||||||
): Transaction {
|
|
||||||
const whenData = serializeDate(when);
|
|
||||||
const data = Buffer.alloc(4 + whenData.length);
|
|
||||||
|
|
||||||
data.writeUInt32LE(1, 0); // ApplyTimestamp instruction
|
|
||||||
whenData.copy(data, 4);
|
|
||||||
|
|
||||||
return new Transaction().add({
|
|
||||||
keys: [
|
|
||||||
{pubkey: from, isSigner: true, isWritable: true},
|
|
||||||
{pubkey: program, isSigner: false, isWritable: true},
|
|
||||||
{pubkey: to, isSigner: false, isWritable: true},
|
|
||||||
],
|
|
||||||
programId: this.programId,
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a transaction that applies a signature, which could enable a
|
|
||||||
* pending payment to proceed.
|
|
||||||
*/
|
|
||||||
static applySignature(
|
|
||||||
from: PublicKey,
|
|
||||||
program: PublicKey,
|
|
||||||
to: PublicKey,
|
|
||||||
): Transaction {
|
|
||||||
const dataLayout = BufferLayout.struct([BufferLayout.u32('instruction')]);
|
|
||||||
|
|
||||||
const data = Buffer.alloc(dataLayout.span);
|
|
||||||
dataLayout.encode(
|
|
||||||
{
|
|
||||||
instruction: 2, // ApplySignature instruction
|
|
||||||
},
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Transaction().add({
|
|
||||||
keys: [
|
|
||||||
{pubkey: from, isSigner: true, isWritable: true},
|
|
||||||
{pubkey: program, isSigner: false, isWritable: true},
|
|
||||||
{pubkey: to, isSigner: false, isWritable: true},
|
|
||||||
],
|
|
||||||
programId: this.programId,
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
export {Account} from './account';
|
export {Account} from './account';
|
||||||
export {BpfLoader} from './bpf-loader';
|
export {BpfLoader} from './bpf-loader';
|
||||||
export {BudgetProgram} from './budget-program';
|
|
||||||
export {Connection} from './connection';
|
export {Connection} from './connection';
|
||||||
export {Loader} from './loader';
|
export {Loader} from './loader';
|
||||||
export {Message} from './message';
|
export {Message} from './message';
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import {Account} from '../src/account';
|
|
||||||
import {BudgetProgram} from '../src/budget-program';
|
|
||||||
|
|
||||||
test('pay', () => {
|
|
||||||
const from = new Account();
|
|
||||||
const program = new Account();
|
|
||||||
const to = new Account();
|
|
||||||
let transaction;
|
|
||||||
|
|
||||||
transaction = BudgetProgram.pay(
|
|
||||||
from.publicKey,
|
|
||||||
program.publicKey,
|
|
||||||
to.publicKey,
|
|
||||||
123,
|
|
||||||
);
|
|
||||||
expect(transaction.instructions[0].keys).toHaveLength(2);
|
|
||||||
expect(transaction.instructions[1].keys).toHaveLength(2);
|
|
||||||
// TODO: Validate transaction contents more
|
|
||||||
|
|
||||||
transaction = BudgetProgram.pay(
|
|
||||||
from.publicKey,
|
|
||||||
program.publicKey,
|
|
||||||
to.publicKey,
|
|
||||||
123,
|
|
||||||
BudgetProgram.signatureCondition(from.publicKey),
|
|
||||||
);
|
|
||||||
expect(transaction.instructions[0].keys).toHaveLength(2);
|
|
||||||
expect(transaction.instructions[1].keys).toHaveLength(1);
|
|
||||||
// TODO: Validate transaction contents more
|
|
||||||
|
|
||||||
transaction = BudgetProgram.pay(
|
|
||||||
from.publicKey,
|
|
||||||
program.publicKey,
|
|
||||||
to.publicKey,
|
|
||||||
123,
|
|
||||||
BudgetProgram.signatureCondition(from.publicKey),
|
|
||||||
BudgetProgram.timestampCondition(from.publicKey, new Date()),
|
|
||||||
);
|
|
||||||
expect(transaction.instructions[0].keys).toHaveLength(2);
|
|
||||||
expect(transaction.instructions[1].keys).toHaveLength(1);
|
|
||||||
// TODO: Validate transaction contents more
|
|
||||||
|
|
||||||
transaction = BudgetProgram.payOnBoth(
|
|
||||||
from.publicKey,
|
|
||||||
program.publicKey,
|
|
||||||
to.publicKey,
|
|
||||||
123,
|
|
||||||
BudgetProgram.signatureCondition(from.publicKey),
|
|
||||||
BudgetProgram.timestampCondition(from.publicKey, new Date()),
|
|
||||||
);
|
|
||||||
expect(transaction.instructions[0].keys).toHaveLength(2);
|
|
||||||
expect(transaction.instructions[1].keys).toHaveLength(1);
|
|
||||||
// TODO: Validate transaction contents more
|
|
||||||
});
|
|
||||||
|
|
||||||
test('apply', () => {
|
|
||||||
const from = new Account();
|
|
||||||
const program = new Account();
|
|
||||||
const to = new Account();
|
|
||||||
let transaction;
|
|
||||||
|
|
||||||
transaction = BudgetProgram.applyTimestamp(
|
|
||||||
from.publicKey,
|
|
||||||
program.publicKey,
|
|
||||||
to.publicKey,
|
|
||||||
new Date(),
|
|
||||||
);
|
|
||||||
expect(transaction.keys).toHaveLength(3);
|
|
||||||
// TODO: Validate transaction contents more
|
|
||||||
|
|
||||||
transaction = BudgetProgram.applySignature(
|
|
||||||
from.publicKey,
|
|
||||||
program.publicKey,
|
|
||||||
to.publicKey,
|
|
||||||
);
|
|
||||||
expect(transaction.keys).toHaveLength(3);
|
|
||||||
// TODO: Validate transaction contents more
|
|
||||||
});
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
BudgetProgram,
|
|
||||||
Connection,
|
Connection,
|
||||||
|
StakeProgram,
|
||||||
SystemInstruction,
|
SystemInstruction,
|
||||||
SystemProgram,
|
SystemProgram,
|
||||||
Transaction,
|
Transaction,
|
||||||
|
@ -26,8 +26,8 @@ test('createAccount', () => {
|
||||||
fromPubkey: new Account().publicKey,
|
fromPubkey: new Account().publicKey,
|
||||||
newAccountPubkey: new Account().publicKey,
|
newAccountPubkey: new Account().publicKey,
|
||||||
lamports: 123,
|
lamports: 123,
|
||||||
space: BudgetProgram.space,
|
space: 0,
|
||||||
programId: BudgetProgram.programId,
|
programId: SystemProgram.programId,
|
||||||
};
|
};
|
||||||
const transaction = SystemProgram.createAccount(params);
|
const transaction = SystemProgram.createAccount(params);
|
||||||
expect(transaction.instructions).toHaveLength(1);
|
expect(transaction.instructions).toHaveLength(1);
|
||||||
|
@ -110,8 +110,8 @@ test('createAccountWithSeed', () => {
|
||||||
basePubkey: fromPubkey,
|
basePubkey: fromPubkey,
|
||||||
seed: 'hi there',
|
seed: 'hi there',
|
||||||
lamports: 123,
|
lamports: 123,
|
||||||
space: BudgetProgram.space,
|
space: 0,
|
||||||
programId: BudgetProgram.programId,
|
programId: SystemProgram.programId,
|
||||||
};
|
};
|
||||||
const transaction = SystemProgram.createAccountWithSeed(params);
|
const transaction = SystemProgram.createAccountWithSeed(params);
|
||||||
expect(transaction.instructions).toHaveLength(1);
|
expect(transaction.instructions).toHaveLength(1);
|
||||||
|
@ -228,7 +228,6 @@ test('nonceAuthorize', () => {
|
||||||
|
|
||||||
test('non-SystemInstruction error', () => {
|
test('non-SystemInstruction error', () => {
|
||||||
const from = new Account();
|
const from = new Account();
|
||||||
const program = new Account();
|
|
||||||
const to = new Account();
|
const to = new Account();
|
||||||
|
|
||||||
const badProgramId = {
|
const badProgramId = {
|
||||||
|
@ -236,7 +235,7 @@ test('non-SystemInstruction error', () => {
|
||||||
{pubkey: from.publicKey, isSigner: true, isWritable: true},
|
{pubkey: from.publicKey, isSigner: true, isWritable: true},
|
||||||
{pubkey: to.publicKey, isSigner: false, isWritable: true},
|
{pubkey: to.publicKey, isSigner: false, isWritable: true},
|
||||||
],
|
],
|
||||||
programId: BudgetProgram.programId,
|
programId: StakeProgram.programId,
|
||||||
data: Buffer.from([2, 0, 0, 0]),
|
data: Buffer.from([2, 0, 0, 0]),
|
||||||
};
|
};
|
||||||
expect(() => {
|
expect(() => {
|
||||||
|
@ -245,16 +244,10 @@ test('non-SystemInstruction error', () => {
|
||||||
);
|
);
|
||||||
}).toThrow();
|
}).toThrow();
|
||||||
|
|
||||||
const amount = 123;
|
const stakePubkey = new Account().publicKey;
|
||||||
const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash
|
const authorizedPubkey = new Account().publicKey;
|
||||||
const budgetPay = BudgetProgram.pay(
|
const params = {stakePubkey, authorizedPubkey};
|
||||||
from.publicKey,
|
const transaction = StakeProgram.deactivate(params);
|
||||||
program.publicKey,
|
|
||||||
to.publicKey,
|
|
||||||
amount,
|
|
||||||
);
|
|
||||||
const transaction = new Transaction({recentBlockhash}).add(budgetPay);
|
|
||||||
transaction.sign(from);
|
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
SystemInstruction.decodeInstructionType(transaction.instructions[1]);
|
SystemInstruction.decodeInstructionType(transaction.instructions[1]);
|
||||||
|
|
Loading…
Reference in New Issue