mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-17 07:11:36 +02:00
[CONTRACTS] Contracts branch update for 1.80 (#718)
* Simple contracts restructured v3 (#675) * Add prototype contract implementation Lacks: - Payment proofs (add early payment proofs) - Tests - Better structure * Separate contract utilities * Simplify the main setup/sign code flow * Remove commented initial_sec_key assignment * Simplify sign::compute function * Add logic for "removal" of secret keys from the context * Further simplify a bit sign and setup * No need for mutable context when adding outputs * Refactor the commented code (lol) * Refactor a bit * tmp --------- Co-authored-by: oryhp <gtrphyro@gmail.com> * contract test fixes (#676) * [Contracts] Experimental Slate v5 (#677) * beginning to add and modify slate version 5 * completion of conversions from V5 to V4 Slate * timestamp and memo fields * upgrade/downgrade serialization of v5 slates * add v5 binary slate versions + start of tests * add bin slate ser/deser to tests * ensure serialization of timestamp always excludes milliseconds * start to update v5 tests, update v5 documentation * add fn to generate populated internal slate for conversion testing * add basic tests to convert all slate versions * update * commit cargo.lock * [WIP] [Contracts] Early payment proofs (#681) * add types and beginnings of signature utils * add proof serialization * serialisation of proof data + signature operation * add serialization type for invoice proof + separate bin wrapper version * add witness data + serializion to invoice payment proof, insert verfication functions in place in order to begin verification testing * tests and infrastructure in place for validation * verification of promise sig * added verification of promise signature, infrastructure up to the point where a signature must be subtracted * attempting to figure out differences between recipient nonce that's getting stored and calculated recipient nonce * implementation of witness verification function, retrieve relevant values and re-validate derived recipient partial signature * move stored portion of invoice proof into core types for storage, need to rename invoice proof * define/refine the stored portion of payment proofs type 2? * Folding all proof data into tx log entry storage * back to importing master * remove cargo files from diffs * remove a lot of extra debug output * return proof witness as part of proof retrieval, define json serialization of invoice proof + witness fields * finish adding verification steps to foreign API * remove redundant promise sig field * move lcation of sign/verify calls * Replace Azure Pipelines with Github Actions (#688) * Update CI Badge on README.MD (#690) * Trigger CI on push and pull request (#693) * Update versioning to 5.2.0-beta.1 against grin 5.2.0-beta.3 (#691) * update versioning to 5.2.0-beta.1 against grin 5.2.0-beta.3 * tweak for CI trigger --------- Co-authored-by: Quentin Le Sceller <q.lesceller@gmail.com> --------- Co-authored-by: Quentin Le Sceller <q.lesceller@gmail.com> * fix Cargo.lock * Contracts - Test fixes + warning cleanups (#694) * clean up warnings in libwallet crate * clean up warnings in controller crate * update all contract tests with awareness of new proof structure * added explicit payment proof tests for both rsr and srs (#695) * Basic mwmixnet send (#696) * integrating onion library * updates and changes to support newly included mwmixnet types * add (incorrect) owner api function * turn off test for now * switch working grin branch to master * fix doctests for build * update cargo lock in attempt to fix croaring build on CI server * update cargo lock with upstream thiserror crate * update test dependency for croaring * [Contracts] Add self spend transaction state (#697) * Add self spend transaction state * subtle errors with output states and tx lookups - fixes * [Contracts] Slatepack v5 Deserialization fix (#698) * add V5 deserialization test + fixes * clarify comment * upwrap fix during v4 deserialization * further unwrap removal * update cargo lock, use latest grin packages * addition of self-spend cancel + tests (#699) * add tests + legacy self send cancel (#702) * [Contracts] Cancel self-spend TX Pt. 2 (#703) * add tests + legacy self send cancel * add missing file * Update grin to latest master, change versioning label to include contracts (#712) * Add owner_api_listen_interface as hidden configuration field (#714) * updates for rust 1.80 * updates for rust 1.80 --------- Co-authored-by: oryhp <gtrphyro@gmail.com> Co-authored-by: Quentin Le Sceller <q.lesceller@gmail.com>
This commit is contained in:
co-authored by
oryhp
Quentin Le Sceller
parent
765f11b168
commit
cdd63fb565
+11
-11
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "grin_wallet_controller"
|
||||
version = "5.4.0-alpha.1"
|
||||
version = "5.4.0-contracts.0"
|
||||
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
|
||||
description = "Controllers for grin wallet instantiation"
|
||||
license = "Apache-2.0"
|
||||
@@ -30,19 +30,19 @@ lazy_static = "1"
|
||||
thiserror = "1"
|
||||
qr_code = "1.1.0"
|
||||
|
||||
grin_wallet_util = { path = "../util", version = "5.4.0-alpha.1" }
|
||||
grin_wallet_api = { path = "../api", version = "5.4.0-alpha.1" }
|
||||
grin_wallet_impls = { path = "../impls", version = "5.4.0-alpha.1" }
|
||||
grin_wallet_libwallet = { path = "../libwallet", version = "5.4.0-alpha.1" }
|
||||
grin_wallet_config = { path = "../config", version = "5.4.0-alpha.1" }
|
||||
grin_wallet_util = { path = "../util", version = "5.4.0-contracts.0" }
|
||||
grin_wallet_api = { path = "../api", version = "5.4.0-contracts.0" }
|
||||
grin_wallet_impls = { path = "../impls", version = "5.4.0-contracts.0" }
|
||||
grin_wallet_libwallet = { path = "../libwallet", version = "5.4.0-contracts.0" }
|
||||
grin_wallet_config = { path = "../config", version = "5.4.0-contracts.0" }
|
||||
|
||||
##### Grin Imports
|
||||
|
||||
# For Release
|
||||
# grin_core = "5.4.0-alpha.1"
|
||||
# grin_keychain = "5.4.0-alpha.1"
|
||||
# grin_util = "5.4.0-alpha.1"
|
||||
# grin_api = "5.4.0-alpha.1"
|
||||
# grin_core = "5.4.0-contracts.0"
|
||||
# grin_keychain = "5.4.0-contracts.0"
|
||||
# grin_util = "5.4.0-contracts.0"
|
||||
# grin_api = "5.4.0-contracts.0"
|
||||
|
||||
# For beta release
|
||||
|
||||
@@ -72,7 +72,7 @@ remove_dir_all = "0.7"
|
||||
##### Grin Imports
|
||||
|
||||
# For Release
|
||||
# grin_chain = "5.4.0-alpha.1"
|
||||
# grin_chain = "5.4.0-contracts.0"
|
||||
|
||||
# For beta release
|
||||
|
||||
|
||||
+480
-1
@@ -13,7 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//! Grin wallet command-line function implementations
|
||||
|
||||
use crate::api::TLSConfig;
|
||||
use crate::apiwallet::{try_slatepack_sync_workflow, Owner};
|
||||
use crate::config::{TorConfig, WalletConfig, WALLET_CONFIG_FILE_NAME};
|
||||
@@ -22,6 +21,11 @@ use crate::error::Error;
|
||||
use crate::impls::PathToSlatepack;
|
||||
use crate::impls::SlateGetter as _;
|
||||
use crate::keychain;
|
||||
use crate::libwallet::api_impl::owner;
|
||||
use crate::libwallet::contract::can_finalize;
|
||||
use crate::libwallet::contract::types::{
|
||||
ContractNewArgsAPI, ContractRevokeArgsAPI, ContractSetupArgsAPI, OutputSelectionArgs,
|
||||
};
|
||||
use crate::libwallet::{
|
||||
self, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateState, Slatepack,
|
||||
SlatepackAddress, Slatepacker, SlatepackerArgs, WalletLCProvider,
|
||||
@@ -31,9 +35,12 @@ use crate::util::{Mutex, ZeroingString};
|
||||
use crate::{controller, display};
|
||||
use ::core::time;
|
||||
use qr_code::QrCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json as json;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
@@ -558,6 +565,140 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct SlatepackOut {
|
||||
/// Is slatepack encrypted
|
||||
pub is_encrypted: bool,
|
||||
/// Is slatepack finalized
|
||||
pub is_finalized: bool,
|
||||
/// File where slatepack is saved
|
||||
pub out_file: String,
|
||||
/// Slatepack message. Encrypted or not.
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for SlatepackOut {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let start_meta = "--------------- SLATEPACK METADATA --------------";
|
||||
let meta = format!(
|
||||
"Slate encrypted: {}\nSlate finalized: {}\nSlate saved to file: {}",
|
||||
self.is_encrypted, self.is_finalized, self.out_file
|
||||
);
|
||||
let start_slatepack = "-------------- CUT BELOW THIS LINE --------------";
|
||||
let end_slatepack = "-------------- CUT ABOVE THIS LINE --------------";
|
||||
write!(
|
||||
f,
|
||||
"{start_meta}\n\n{meta}\n\n{start_slatepack}\n\n{}\n\n{end_slatepack}",
|
||||
self.message
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SlatepackOut {
|
||||
fn as_json(&self) -> String {
|
||||
serde_json::to_string_pretty(&self).unwrap()
|
||||
}
|
||||
|
||||
pub fn print(&self, as_json: bool) -> () {
|
||||
if !self.is_finalized {
|
||||
if as_json {
|
||||
println!("{}", self.as_json());
|
||||
} else {
|
||||
println!("{}", self);
|
||||
}
|
||||
} else {
|
||||
println!("Transaction was broadcasted."); // TODO: as_json makes no sense here, fix later.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_slatepack<L, C, K>(
|
||||
api: &mut Owner<L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
slate: &Slate,
|
||||
counterparty_addr: &str,
|
||||
out_file: Option<String>,
|
||||
as_json: bool,
|
||||
) -> ()
|
||||
where
|
||||
L: WalletLCProvider<'static, C, K> + 'static,
|
||||
C: NodeClient + 'static,
|
||||
K: keychain::Keychain + 'static,
|
||||
{
|
||||
// For now, we don't compact slates with sl.compact(). We first make them work without compaction.
|
||||
let slate_out =
|
||||
prepare_slatepack(api, keychain_mask, &slate, &counterparty_addr, out_file).unwrap();
|
||||
slate_out.print(as_json);
|
||||
}
|
||||
|
||||
pub fn prepare_slatepack<L, C, K>(
|
||||
owner_api: &mut Owner<L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
slate: &Slate,
|
||||
dest: &str,
|
||||
out_file_override: Option<String>,
|
||||
) -> Result<SlatepackOut, libwallet::Error>
|
||||
where
|
||||
L: WalletLCProvider<'static, C, K> + 'static,
|
||||
C: NodeClient + 'static,
|
||||
K: keychain::Keychain + 'static,
|
||||
{
|
||||
// Same as output_slatepack except that we don't write to stdout, care about locking or whether the slate was finalized.
|
||||
|
||||
// Output the slatepack file to stdout and to a file
|
||||
let mut message = String::from("");
|
||||
let mut address = None;
|
||||
let mut tld = String::from("");
|
||||
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
|
||||
address = match SlatepackAddress::try_from(dest) {
|
||||
Ok(a) => Some(a),
|
||||
Err(_) => None,
|
||||
};
|
||||
// encrypt for recipient by default
|
||||
let recipients = match address.clone() {
|
||||
Some(a) => vec![a],
|
||||
None => vec![],
|
||||
};
|
||||
// TODO: what is sender_index?
|
||||
message = api.create_slatepack_message(m, &slate, Some(0), recipients)?;
|
||||
// Trim the \n at the end.
|
||||
let len_withoutcrlf = message.trim_end().len();
|
||||
message.truncate(len_withoutcrlf);
|
||||
|
||||
tld = api.get_top_level_directory()?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// create a directory to which files will be output
|
||||
let slate_dir = format!("{}/{}", tld, "slatepack");
|
||||
let _ = std::fs::create_dir_all(slate_dir.clone());
|
||||
let out_file_name = match out_file_override {
|
||||
None => format!("{}/{}.{}.slatepack", slate_dir, slate.id, slate.state),
|
||||
Some(f) => f,
|
||||
};
|
||||
|
||||
let mut output = File::create(out_file_name.clone())?;
|
||||
output.write_all(&message.as_bytes())?;
|
||||
output.sync_all()?;
|
||||
|
||||
// Since we always finalize if we can, we can also use this to know if the tx is finalized
|
||||
let is_finalized = can_finalize(slate);
|
||||
|
||||
let slate_out = SlatepackOut {
|
||||
is_encrypted: address.is_some(),
|
||||
is_finalized: is_finalized,
|
||||
out_file: out_file_name,
|
||||
message: message,
|
||||
};
|
||||
|
||||
// TODO: We save the slatepack, but it is encrypted for the counterparty. It seems hard to
|
||||
// know which slatepack is which if we can't decrypt them. Either add some more metadata
|
||||
// to slatepacks e.g. timestamp, counterparty address or save also a version that is encrypted with
|
||||
// our own address so we can view it.
|
||||
|
||||
Ok(slate_out)
|
||||
}
|
||||
|
||||
// Parse a slate and slatepack from a message
|
||||
pub fn parse_slatepack<L, C, K>(
|
||||
owner_api: &mut Owner<L, C, K>,
|
||||
@@ -1474,3 +1615,341 @@ where
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create new contract command arguments
|
||||
#[derive(Clone)]
|
||||
pub struct ContractNewArgs {
|
||||
/// Address of the counterparty
|
||||
pub counterparty_addr: String,
|
||||
/// Receive amount
|
||||
pub receive: Option<u64>,
|
||||
/// Send amount
|
||||
pub send: Option<u64>,
|
||||
/// The human readable account name from which to draw outputs
|
||||
/// for the transaction, overriding whatever the active account is as set via the
|
||||
/// [`set_active_account`](../grin_wallet_api/owner/struct.Owner.html#method.set_active_account) method.
|
||||
pub src_acct_name: Option<String>,
|
||||
/// Number of participants in a contract (either 1 or 2)
|
||||
pub num_participants: u8,
|
||||
/// Show the resulting slatepack as JSON
|
||||
pub as_json: bool,
|
||||
/// Use the specified inputs (comma separated input commitments)
|
||||
pub use_inputs: Option<String>,
|
||||
/// How to separate outputs (command separated amounts)
|
||||
pub make_outputs: Option<String>,
|
||||
|
||||
// Future features
|
||||
/// Custom fee contribution
|
||||
pub fee_rate: Option<u32>,
|
||||
/// Save slatepack to a specific filename
|
||||
pub outfile: Option<String>,
|
||||
/// Select outputs early
|
||||
pub add_outputs: bool,
|
||||
}
|
||||
|
||||
impl ContractNewArgs {
|
||||
fn get_net_change(&self) -> i64 {
|
||||
// TODO: could the cast 'as i64' overflow or something?
|
||||
match self.receive {
|
||||
None => match self.send {
|
||||
None => panic!("Send or receive not specified."),
|
||||
Some(v) => -(v as i64), // negative net change on send
|
||||
},
|
||||
Some(v) => v as i64, // positive net change on receive
|
||||
}
|
||||
}
|
||||
|
||||
// Create a ContractNewArgsAPI from the ContractNewArgs
|
||||
fn to_api_args(&self) -> ContractNewArgsAPI {
|
||||
let net_change = self.get_net_change();
|
||||
ContractNewArgsAPI {
|
||||
setup_args: ContractSetupArgsAPI {
|
||||
src_acct_name: match self.src_acct_name.as_ref() {
|
||||
Some(v) => Some(v.to_string()),
|
||||
None => None,
|
||||
},
|
||||
net_change: Some(net_change),
|
||||
num_participants: self.num_participants,
|
||||
add_outputs: self.add_outputs,
|
||||
selection_args: OutputSelectionArgs {
|
||||
use_inputs: match self.use_inputs.as_ref() {
|
||||
Some(v) => Some(v.to_string()),
|
||||
None => None,
|
||||
},
|
||||
make_outputs: match self.make_outputs.as_ref() {
|
||||
Some(v) => Some(v.to_string()),
|
||||
None => None,
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
proof_args: Default::default(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contract_new<L, C, K>(
|
||||
owner_api: &mut Owner<L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
args: ContractNewArgs,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
L: WalletLCProvider<'static, C, K>,
|
||||
C: NodeClient + 'static,
|
||||
K: keychain::Keychain + 'static,
|
||||
{
|
||||
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
|
||||
let contract_new_args = args.to_api_args();
|
||||
|
||||
let slate = api.contract_new(m, &contract_new_args)?;
|
||||
|
||||
print_slatepack(
|
||||
api,
|
||||
keychain_mask,
|
||||
&slate,
|
||||
&args.counterparty_addr,
|
||||
args.outfile,
|
||||
args.as_json,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sign contract command argument
|
||||
#[derive(Clone)]
|
||||
pub struct ContractSetupArgs {
|
||||
/// Address of the counterparty
|
||||
pub counterparty_addr: Option<String>,
|
||||
/// Receive amount
|
||||
pub receive: Option<u64>,
|
||||
/// Send amount
|
||||
pub send: Option<u64>,
|
||||
/// Show the resulting slatepack as JSON
|
||||
pub as_json: bool,
|
||||
/// Use the specified inputs (comma separated input commitments)
|
||||
pub use_inputs: Option<String>,
|
||||
/// How to separate outputs (command separated amounts)
|
||||
pub make_outputs: Option<String>,
|
||||
|
||||
// Future features
|
||||
/// Whether we should automatically sign a receive of any value
|
||||
// pub auto_receive: Option<bool>,
|
||||
/// Custom fee contribution
|
||||
pub fee_rate: Option<u32>,
|
||||
/// Save slatepack to a specific filename
|
||||
pub outfile: Option<String>,
|
||||
/// Add outputs
|
||||
pub add_outputs: bool, // lock outputs early
|
||||
}
|
||||
|
||||
impl ContractSetupArgs {
|
||||
fn get_net_change(&self) -> Option<i64> {
|
||||
let mut net_change: Option<i64> = None;
|
||||
// TODO: Check bounds before casting to i64.
|
||||
if self.receive.is_some() && self.send.is_some() {
|
||||
panic!("Can't pass both --receive and --send parameters.");
|
||||
}
|
||||
if self.receive.is_some() {
|
||||
net_change = Some(self.receive.unwrap() as i64);
|
||||
}
|
||||
if self.send.is_some() {
|
||||
net_change = Some(-(self.send.unwrap() as i64));
|
||||
}
|
||||
net_change
|
||||
}
|
||||
|
||||
// Create a ContractSetupArgsAPI from the ContractSetupArgs
|
||||
fn to_api_args(&self) -> ContractSetupArgsAPI {
|
||||
let net_change = self.get_net_change();
|
||||
ContractSetupArgsAPI {
|
||||
// TODO: num_participants is derived here. It should be an Option and read from the slate.
|
||||
// Need to check no attack are possible regarding kernel fee contribution.
|
||||
net_change: net_change,
|
||||
add_outputs: self.add_outputs,
|
||||
selection_args: OutputSelectionArgs {
|
||||
use_inputs: match self.use_inputs.as_ref() {
|
||||
Some(v) => Some(v.to_string()),
|
||||
None => None,
|
||||
},
|
||||
make_outputs: match self.make_outputs.as_ref() {
|
||||
Some(v) => Some(v.to_string()),
|
||||
None => None,
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn contract_setup<L, C, K>(
|
||||
// owner_api: &mut Owner<L, C, K>,
|
||||
// keychain_mask: Option<&SecretKey>,
|
||||
// args: ContractSetupArgs,
|
||||
// ) -> Result<(), Error>
|
||||
// where
|
||||
// L: WalletLCProvider<'static, C, K>,
|
||||
// C: NodeClient + 'static,
|
||||
// K: keychain::Keychain + 'static,
|
||||
// {
|
||||
// controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
|
||||
// // Read the slatepack from stdin
|
||||
// println!("Paste slatepack:");
|
||||
// let mut slatepack_msg = String::new();
|
||||
// io::stdin()
|
||||
// .read_line(&mut slatepack_msg)
|
||||
// .expect("Failed to read from stdin");
|
||||
|
||||
// let mut contract_setup_args = args.to_api_args();
|
||||
|
||||
// // Decrypt the slate, perform setup on it and encrypt it for the next party
|
||||
// // TODO: Make sure you get the counterparty_addr and slate with 1 call.
|
||||
// let slatepack = owner::decode_slatepack_message(
|
||||
// api.wallet_inst.clone(),
|
||||
// keychain_mask,
|
||||
// String::from(slatepack_msg.clone()),
|
||||
// vec![0],
|
||||
// )?;
|
||||
|
||||
// let counterparty_addr =
|
||||
// if args.counterparty_addr.is_some() {
|
||||
// args.counterparty_addr.unwrap()
|
||||
// } else {
|
||||
// if !slatepack.sender.is_some() {
|
||||
// panic!("No address to encrypt for. Contracts only support encrypted slates right now.");
|
||||
// }
|
||||
// String::try_from(&slatepack.sender.unwrap())?
|
||||
// };
|
||||
// let mut slate = owner::slate_from_slatepack_message(
|
||||
// api.wallet_inst.clone(),
|
||||
// keychain_mask,
|
||||
// String::from(slatepack_msg),
|
||||
// vec![0],
|
||||
// )?;
|
||||
// // We read the number of participants from the slate that was already created. We need this to
|
||||
// // avoid taking the default value of 2 in case of 3-party computation and thus incorrectly computing
|
||||
// // our kernel cost contribution.
|
||||
// contract_setup_args.num_participants = slate.num_participants;
|
||||
|
||||
// slate = api.contract_setup(m, &slate, &contract_setup_args)?;
|
||||
|
||||
// print_slatepack(
|
||||
// api,
|
||||
// keychain_mask,
|
||||
// &slate,
|
||||
// &counterparty_addr,
|
||||
// args.outfile,
|
||||
// args.as_json,
|
||||
// );
|
||||
|
||||
// Ok(())
|
||||
// })?;
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
pub fn contract_sign<L, C, K>(
|
||||
owner_api: &mut Owner<L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
args: ContractSetupArgs,
|
||||
broadcast_tx: bool,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
L: WalletLCProvider<'static, C, K>,
|
||||
C: NodeClient + 'static,
|
||||
K: keychain::Keychain + 'static,
|
||||
{
|
||||
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
|
||||
// Read the slatepack from stdin
|
||||
println!("Paste slatepack:");
|
||||
let mut slatepack_msg = String::new();
|
||||
io::stdin()
|
||||
.read_line(&mut slatepack_msg)
|
||||
.expect("Failed to read from stdin");
|
||||
|
||||
// Args for signing are just setup args
|
||||
let contract_sign_args = args.to_api_args();
|
||||
|
||||
// Decrypt the slate, sign it and encrypt it for the next party
|
||||
// TODO: Make sure you get the counterparty_addr and slate with 1 call.
|
||||
let slatepack = owner::decode_slatepack_message(
|
||||
api.wallet_inst.clone(),
|
||||
keychain_mask,
|
||||
String::from(slatepack_msg.clone()),
|
||||
vec![0],
|
||||
)?;
|
||||
|
||||
let counterparty_addr =
|
||||
if args.counterparty_addr.is_some() {
|
||||
args.counterparty_addr.unwrap()
|
||||
} else {
|
||||
if !slatepack.sender.is_some() {
|
||||
panic!("No address to encrypt for. Contracts only support encrypted slates right now.");
|
||||
}
|
||||
String::try_from(&slatepack.sender.unwrap())?
|
||||
};
|
||||
let mut slate = owner::slate_from_slatepack_message(
|
||||
api.wallet_inst.clone(),
|
||||
keychain_mask,
|
||||
String::from(slatepack_msg),
|
||||
vec![0],
|
||||
)?;
|
||||
|
||||
slate = api.contract_sign(m, &slate, &contract_sign_args)?;
|
||||
|
||||
print_slatepack(
|
||||
api,
|
||||
keychain_mask,
|
||||
&slate,
|
||||
&counterparty_addr,
|
||||
args.outfile,
|
||||
args.as_json,
|
||||
);
|
||||
|
||||
if broadcast_tx {
|
||||
let is_finalized = can_finalize(&slate);
|
||||
if is_finalized {
|
||||
api.post_tx(keychain_mask, &slate, true)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ContractRevokeArgs {
|
||||
/// Id of a transaction we want to cancel
|
||||
pub tx_id: u32,
|
||||
}
|
||||
|
||||
pub fn contract_revoke<L, C, K>(
|
||||
owner_api: &mut Owner<L, C, K>,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
args: ContractRevokeArgs,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
L: WalletLCProvider<'static, C, K>,
|
||||
C: NodeClient + 'static,
|
||||
K: keychain::Keychain + 'static,
|
||||
{
|
||||
controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| {
|
||||
let slate_opt = api.contract_revoke(m, &ContractRevokeArgsAPI { tx_id: args.tx_id })?;
|
||||
// TODO: replace dest="nope" with our own address and add --as-json support
|
||||
if slate_opt.is_some() {
|
||||
let slate_out =
|
||||
prepare_slatepack(api, keychain_mask, &slate_opt.unwrap(), "nope", None)?;
|
||||
println!("{}", slate_out);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -15,18 +15,24 @@
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
extern crate grin_wallet_libwallet as libwallet;
|
||||
extern crate log;
|
||||
|
||||
use grin_chain as chain;
|
||||
use grin_core as core;
|
||||
use grin_keychain as keychain;
|
||||
use grin_util as util;
|
||||
|
||||
use self::core::global;
|
||||
use self::core::global::ChainTypes;
|
||||
use self::keychain::ExtKeychain;
|
||||
use self::keychain::{ExtKeychain, Keychain};
|
||||
use self::libwallet::WalletInst;
|
||||
use impls::test_framework::{LocalWalletClient, WalletProxy};
|
||||
use chain::Chain;
|
||||
use grin_wallet_controller::Error;
|
||||
use impls::test_framework::{self, LocalWalletClient, WalletProxy};
|
||||
use impls::{DefaultLCProvider, DefaultWalletImpl};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use util::secp::key::SecretKey;
|
||||
use util::{Mutex, ZeroingString};
|
||||
|
||||
@@ -176,3 +182,133 @@ pub fn open_local_wallet(
|
||||
.unwrap();
|
||||
(Arc::new(Mutex::new(wallet)), mask)
|
||||
}
|
||||
|
||||
// Creates the given number of wallets and spawns a thread that runs the wallet proxy
|
||||
#[allow(dead_code)]
|
||||
pub fn create_wallets(
|
||||
wallets_def: Vec<Vec<(&'static str, u64)>>, // a vector of boolean that represent whether we mine into a wallet
|
||||
test_dir: &'static str,
|
||||
) -> Result<
|
||||
(
|
||||
Vec<(
|
||||
Arc<
|
||||
Mutex<
|
||||
Box<
|
||||
dyn WalletInst<
|
||||
'static,
|
||||
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
|
||||
LocalWalletClient,
|
||||
ExtKeychain,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
Option<SecretKey>,
|
||||
)>, // wallets
|
||||
Arc<Chain>, // chain
|
||||
Arc<AtomicBool>, // stopper
|
||||
u64, // block height
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
// Create a new proxy to simulate server and wallet responses
|
||||
let mut wallet_proxy = create_wallet_proxy(test_dir);
|
||||
let chain = wallet_proxy.chain.clone();
|
||||
let stopper = wallet_proxy.running.clone();
|
||||
|
||||
let mut wallets = vec![];
|
||||
for i in 0..wallets_def.len() {
|
||||
let name = format!("wallet{}", i + 1);
|
||||
let wclient = LocalWalletClient::new(&name, wallet_proxy.tx.clone());
|
||||
let (wallet1, mask1) = create_local_wallet(
|
||||
test_dir,
|
||||
&name,
|
||||
None,
|
||||
// $seed_phrase.clone(),
|
||||
wclient.clone(),
|
||||
true,
|
||||
);
|
||||
wallet_proxy.add_wallet(
|
||||
&name,
|
||||
wclient.get_send_instance(),
|
||||
wallet1.clone(),
|
||||
mask1.clone(),
|
||||
);
|
||||
wallets.push((wallet1, mask1));
|
||||
}
|
||||
// Set the wallet proxy listener running
|
||||
thread::spawn(move || {
|
||||
if let Err(e) = wallet_proxy.run() {
|
||||
log::error!("Wallet Proxy error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
// // Mine values into wallets
|
||||
// // few values to keep things shorter
|
||||
let reward = core::consensus::REWARD;
|
||||
let mut bh = 0u64;
|
||||
|
||||
for (idx, accs) in wallets_def.iter().enumerate() {
|
||||
let wallet1 = wallets[idx].0.clone();
|
||||
let mask1 = wallets[idx].1.as_ref();
|
||||
|
||||
for (acc_idx, (acc_name, num_mined_blocks)) in accs.iter().enumerate() {
|
||||
// create the account
|
||||
if acc_name.to_string() != "default" {
|
||||
wallet::controller::owner_single_use(
|
||||
Some(wallet1.clone()),
|
||||
mask1,
|
||||
None,
|
||||
|api, m| {
|
||||
let new_path = api.create_account_path(m, acc_name)?;
|
||||
assert_eq!(
|
||||
new_path,
|
||||
ExtKeychain::derive_key_id(2, acc_idx as u32, 0, 0, 0) // NOTE: default should always be at 0 and is already created
|
||||
);
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
// Get some mining done
|
||||
if *num_mined_blocks == 0 {
|
||||
continue;
|
||||
}
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
w.set_parent_key_id_by_name(acc_name)?;
|
||||
}
|
||||
let _ = test_framework::award_blocks_to_wallet(
|
||||
&chain,
|
||||
wallet1.clone(),
|
||||
mask1,
|
||||
*num_mined_blocks as usize,
|
||||
false,
|
||||
);
|
||||
bh += num_mined_blocks;
|
||||
|
||||
// Sanity check wallet 1 contents
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
assert!(wallet1_refreshed);
|
||||
assert_eq!(wallet1_info.last_confirmed_height, bh);
|
||||
assert_eq!(wallet1_info.total, num_mined_blocks * reward);
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
// Sanity check the number of accounts
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
let accounts = api.accounts(m)?;
|
||||
assert_eq!(accounts.len(), accs.len());
|
||||
Ok(())
|
||||
})?;
|
||||
// Set the account on the wallet to "default"
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
w.set_parent_key_id_by_name("default")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((wallets, chain, stopper, bh))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
// Copyright 2022 The Grin Developers
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test contract utils
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
use grin_wallet_util::grin_core as core;
|
||||
use grin_wallet_util::grin_keychain as keychain;
|
||||
use grin_wallet_util::grin_util as util;
|
||||
|
||||
use self::keychain::ExtKeychain;
|
||||
use self::libwallet::WalletInst;
|
||||
// use impls::test_framework::{LocalWalletClient, WalletProxy};
|
||||
use crate::chain::Chain;
|
||||
use grin_wallet_util::grin_chain as chain;
|
||||
use impls::{DefaultLCProvider, DefaultWalletImpl};
|
||||
use std::sync::Arc;
|
||||
use util::secp::key::SecretKey;
|
||||
use util::{Mutex, ZeroingString};
|
||||
|
||||
use impls::test_framework::{self, LocalWalletClient, WalletProxy};
|
||||
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||
use libwallet::{Slate, SlateState};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
// #[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_local_wallet, create_wallet_proxy, setup};
|
||||
|
||||
pub fn create_wallets(
|
||||
test_dir: &'static str,
|
||||
) -> (
|
||||
Vec<(
|
||||
Arc<
|
||||
Mutex<
|
||||
Box<
|
||||
dyn WalletInst<
|
||||
'static,
|
||||
DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
|
||||
LocalWalletClient,
|
||||
ExtKeychain,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
Option<SecretKey>,
|
||||
)>,
|
||||
Arc<Chain>, // chain
|
||||
Arc<AtomicBool>, // stopper
|
||||
) {
|
||||
// Create a new proxy to simulate server and wallet responses
|
||||
let mut wallet_proxy = common::create_wallet_proxy(test_dir);
|
||||
let chain = wallet_proxy.chain.clone();
|
||||
let stopper = wallet_proxy.running.clone();
|
||||
|
||||
let mut wallets = vec![];
|
||||
for i in 0..2 {
|
||||
let name = format!("wallet{}", i + 1);
|
||||
let wclient = LocalWalletClient::new(&name, wallet_proxy.tx.clone());
|
||||
let (wallet1, mask1) = common::create_local_wallet(
|
||||
test_dir,
|
||||
&name,
|
||||
None,
|
||||
// $seed_phrase.clone(),
|
||||
wclient.clone(),
|
||||
true,
|
||||
);
|
||||
wallet_proxy.add_wallet(
|
||||
&name,
|
||||
wclient.get_send_instance(),
|
||||
wallet1.clone(),
|
||||
mask1.clone(),
|
||||
);
|
||||
// create_wallet_and_add!(
|
||||
// client1,
|
||||
// wallet1,
|
||||
// mask1_i,
|
||||
// test_dir,
|
||||
// "wallet1",
|
||||
// None,
|
||||
// &mut wallet_proxy,
|
||||
// true
|
||||
// );
|
||||
wallets.push((wallet1, mask1));
|
||||
}
|
||||
// Set the wallet proxy listener running
|
||||
thread::spawn(move || {
|
||||
if let Err(e) = wallet_proxy.run() {
|
||||
error!("Wallet Proxy error: {}", e);
|
||||
}
|
||||
});
|
||||
(wallets, chain, stopper)
|
||||
|
||||
// let $client = LocalWalletClient::new($name, $proxy.tx.clone());
|
||||
// let ($wallet, $mask) = common::create_local_wallet(
|
||||
// $test_dir,
|
||||
// $name,
|
||||
// $seed_phrase.clone(),
|
||||
// $client.clone(),
|
||||
// $create_mask,
|
||||
// );
|
||||
// $proxy.add_wallet(
|
||||
// $name,
|
||||
// $client.get_send_instance(),
|
||||
// $wallet.clone(),
|
||||
// $mask.clone(),
|
||||
// );
|
||||
}
|
||||
|
||||
// #[macro_export]
|
||||
// macro_rules! create_wallets {
|
||||
// ($client:ident, $wallet: ident, $mask: ident, $test_dir: expr, $name: expr, $seed_phrase: expr, $proxy: expr, $create_mask: expr) => {
|
||||
// // Create a new proxy to simulate server and wallet responses
|
||||
// let mut wallet_proxy = create_wallet_proxy(test_dir);
|
||||
// let chain = wallet_proxy.chain.clone();
|
||||
// let stopper = wallet_proxy.running.clone();
|
||||
|
||||
// let rv = vec![];
|
||||
// for i in 0..wallets.len() {
|
||||
// let name = format!("wallet{}", i + 1);
|
||||
// let wclient = LocalWalletClient::new(name, wallet_proxy.tx.clone());
|
||||
// let (wallet1, mask1) = common::create_local_wallet(
|
||||
// $test_dir,
|
||||
// name,
|
||||
// None,
|
||||
// // $seed_phrase.clone(),
|
||||
// wallet_proxy.clone(),
|
||||
// true,
|
||||
// );
|
||||
// wallet_proxy.add_wallet(
|
||||
// name,
|
||||
// wclient.get_send_instance(),
|
||||
// wallet1.clone(),
|
||||
// mask1.clone(),
|
||||
// );
|
||||
// // create_wallet_and_add!(
|
||||
// // client1,
|
||||
// // wallet1,
|
||||
// // mask1_i,
|
||||
// // test_dir,
|
||||
// // "wallet1",
|
||||
// // None,
|
||||
// // &mut wallet_proxy,
|
||||
// // true
|
||||
// // );
|
||||
// rv.push((wallet1, mask1));
|
||||
// }
|
||||
// rv
|
||||
|
||||
// // let $client = LocalWalletClient::new($name, $proxy.tx.clone());
|
||||
// // let ($wallet, $mask) = common::create_local_wallet(
|
||||
// // $test_dir,
|
||||
// // $name,
|
||||
// // $seed_phrase.clone(),
|
||||
// // $client.clone(),
|
||||
// // $create_mask,
|
||||
// // );
|
||||
// // $proxy.add_wallet(
|
||||
// // $name,
|
||||
// // $client.get_send_instance(),
|
||||
// // $wallet.clone(),
|
||||
// // $mask.clone(),
|
||||
// // );
|
||||
// };
|
||||
// }
|
||||
|
||||
// prepare wallets
|
||||
// fn create_wallets(
|
||||
// wallets: Vec<u64>,
|
||||
// test_dir: &'static str,
|
||||
// // wallet_proxy: WalletProxy<
|
||||
// // 'static,
|
||||
// // DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
|
||||
// // LocalWalletClient,
|
||||
// // ExtKeychain,
|
||||
// // >,
|
||||
// ) -> Vec<(
|
||||
// Arc<
|
||||
// Mutex<
|
||||
// Box<
|
||||
// dyn WalletInst<
|
||||
// 'static,
|
||||
// DefaultLCProvider<'static, LocalWalletClient, ExtKeychain>,
|
||||
// LocalWalletClient,
|
||||
// ExtKeychain,
|
||||
// >,
|
||||
// >,
|
||||
// >,
|
||||
// >,
|
||||
// Option<SecretKey>,
|
||||
// )> {
|
||||
// // Create a new proxy to simulate server and wallet responses
|
||||
// let mut wallet_proxy = create_wallet_proxy(test_dir);
|
||||
// let chain = wallet_proxy.chain.clone();
|
||||
// let stopper = wallet_proxy.running.clone();
|
||||
|
||||
// let rv = vec![];
|
||||
// for _ in 0..wallets.len() {
|
||||
// create_wallet_and_add!(
|
||||
// client1,
|
||||
// wallet1,
|
||||
// mask1_i,
|
||||
// test_dir,
|
||||
// "wallet1",
|
||||
// None,
|
||||
// &mut wallet_proxy,
|
||||
// true
|
||||
// );
|
||||
// rv.push((wallet1, mask1_i));
|
||||
// }
|
||||
// rv
|
||||
// }
|
||||
|
||||
// /// prepare two wallets for testing
|
||||
// fn prepare_wallets(n_wallets: u8, test_dir: &'static str) -> Result<Vec<()>, libwallet::Error> {
|
||||
// // Create a new proxy to simulate server and wallet responses
|
||||
// let mut wallet_proxy = create_wallet_proxy(test_dir);
|
||||
// let chain = wallet_proxy.chain.clone();
|
||||
// let stopper = wallet_proxy.running.clone();
|
||||
|
||||
// create_wallet_and_add!(
|
||||
// client1,
|
||||
// wallet1,
|
||||
// mask1_i,
|
||||
// test_dir,
|
||||
// "wallet1",
|
||||
// None,
|
||||
// &mut wallet_proxy,
|
||||
// true
|
||||
// );
|
||||
// let mask1 = (&mask1_i).as_ref();
|
||||
// create_wallet_and_add!(
|
||||
// client2,
|
||||
// wallet2,
|
||||
// mask2_i,
|
||||
// test_dir,
|
||||
// "wallet2",
|
||||
// None,
|
||||
// &mut wallet_proxy,
|
||||
// true
|
||||
// );
|
||||
// let mask2 = (&mask2_i).as_ref();
|
||||
|
||||
// // Set the wallet proxy listener running
|
||||
// thread::spawn(move || {
|
||||
// if let Err(e) = wallet_proxy.run() {
|
||||
// error!("Wallet Proxy error: {}", e);
|
||||
// }
|
||||
// });
|
||||
|
||||
// // few values to keep things shorter
|
||||
// let reward = core::consensus::REWARD;
|
||||
|
||||
// // add some accounts
|
||||
// wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
// api.create_account_path(m, "mining")?;
|
||||
// api.create_account_path(m, "listener")?;
|
||||
// Ok(())
|
||||
// })?;
|
||||
|
||||
// // Get some mining done
|
||||
// {
|
||||
// wallet_inst!(wallet1, w);
|
||||
// w.set_parent_key_id_by_name("mining")?;
|
||||
// }
|
||||
// let mut bh = 10u64;
|
||||
// let _ =
|
||||
// test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
|
||||
|
||||
// // Sanity check wallet 1 contents
|
||||
// wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
// let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
// assert!(wallet1_refreshed);
|
||||
// assert_eq!(wallet1_info.last_confirmed_height, bh);
|
||||
// assert_eq!(wallet1_info.total, bh * reward);
|
||||
// Ok(())
|
||||
// })?;
|
||||
|
||||
// // let logging finish
|
||||
// stopper.store(false, Ordering::Relaxed);
|
||||
// thread::sleep(Duration::from_millis(200));
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn wallet_contract_rsr_tx() -> Result<(), libwallet::Error> {
|
||||
// let test_dir = "test_output/contract_rsr_tx";
|
||||
// setup(test_dir);
|
||||
// contract_rsr_tx_impl(test_dir)?;
|
||||
// clean_output_dir(test_dir);
|
||||
// Ok(())
|
||||
// }
|
||||
@@ -0,0 +1,259 @@
|
||||
// Copyright 2022 The Grin Developers
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test a wallet doing contracts with different accounts
|
||||
// #[macro_use]
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
extern crate log;
|
||||
|
||||
use grin_core as core;
|
||||
use grin_keychain as keychain;
|
||||
|
||||
use self::core::global;
|
||||
use self::keychain::{ExtKeychain, Keychain};
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
use impls::test_framework::{self};
|
||||
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||
use libwallet::{Slate, SlateState};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallets, setup};
|
||||
|
||||
/// contract accounts testing (mostly the same as accounts.rs)
|
||||
fn contract_accounts_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
// create two wallets with some extra accounts and don't mine anything in them
|
||||
let (wallets, chain, stopper, _bh) = create_wallets(
|
||||
vec![
|
||||
vec![
|
||||
("default", 0),
|
||||
("account1", 0),
|
||||
("account2", 0),
|
||||
("account3", 0),
|
||||
],
|
||||
vec![("default", 0), ("listener_account", 0)],
|
||||
],
|
||||
test_dir,
|
||||
)
|
||||
.unwrap();
|
||||
let wallet1 = wallets[0].0.clone();
|
||||
let mask1 = wallets[0].1.as_ref();
|
||||
let wallet2 = wallets[1].0.clone();
|
||||
let mask2 = wallets[1].1.as_ref();
|
||||
|
||||
// few values to keep things shorter
|
||||
let reward = core::consensus::REWARD;
|
||||
let cm = global::coinbase_maturity(); // assume all testing precedes soft fork height
|
||||
|
||||
// Default wallet 2 to listen on that account
|
||||
{
|
||||
wallet_inst!(wallet2, w);
|
||||
w.set_parent_key_id_by_name("listener_account")?;
|
||||
}
|
||||
|
||||
// Mine into two different accounts in the same wallet
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
w.set_parent_key_id_by_name("account1")?;
|
||||
assert_eq!(w.parent_key_id(), ExtKeychain::derive_key_id(2, 1, 0, 0, 0));
|
||||
}
|
||||
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 7, false);
|
||||
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
w.set_parent_key_id_by_name("account2")?;
|
||||
assert_eq!(w.parent_key_id(), ExtKeychain::derive_key_id(2, 2, 0, 0, 0));
|
||||
}
|
||||
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 5, false);
|
||||
|
||||
// Should have 5 in account1 (5 spendable), 5 in account (2 spendable)
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
assert!(wallet1_refreshed);
|
||||
assert_eq!(wallet1_info.last_confirmed_height, 12);
|
||||
assert_eq!(wallet1_info.total, 5 * reward);
|
||||
assert_eq!(wallet1_info.amount_currently_spendable, (5 - cm) * reward);
|
||||
// check tx log as well
|
||||
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(txs.len(), 5);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// now check second account
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
w.set_parent_key_id_by_name("account1")?;
|
||||
}
|
||||
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
// check last confirmed height on this account is different from above (should be 0)
|
||||
let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?;
|
||||
assert_eq!(wallet1_info.last_confirmed_height, 0);
|
||||
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
assert!(wallet1_refreshed);
|
||||
assert_eq!(wallet1_info.last_confirmed_height, 12);
|
||||
assert_eq!(wallet1_info.total, 7 * reward);
|
||||
assert_eq!(wallet1_info.amount_currently_spendable, 7 * reward);
|
||||
// check tx log as well
|
||||
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(txs.len(), 7);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// should be nothing in default account
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
w.set_parent_key_id_by_name("default")?;
|
||||
}
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?;
|
||||
assert_eq!(wallet1_info.last_confirmed_height, 0);
|
||||
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
assert!(wallet1_refreshed);
|
||||
assert_eq!(wallet1_info.last_confirmed_height, 12);
|
||||
assert_eq!(wallet1_info.total, 0,);
|
||||
assert_eq!(wallet1_info.amount_currently_spendable, 0,);
|
||||
// check tx log as well
|
||||
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(txs.len(), 0);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// TODO: check what send_tx_slate_direct call does in accounts.rs test
|
||||
// TODO: check that you can't call send on the default account because you have no funds
|
||||
|
||||
// Send a tx from wallet1::account1 -> wallet2::listener_account
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
w.set_parent_key_id_by_name("account1")?;
|
||||
}
|
||||
|
||||
let mut slate = Slate::blank(0, true); // this gets overriden below
|
||||
|
||||
let mut sender_address = None;
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
// Send wallet inititates a standard transaction with --send=5
|
||||
let args = &ContractNewArgsAPI {
|
||||
setup_args: ContractSetupArgsAPI {
|
||||
net_change: Some(-5_000_000_000),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_new(m, args)?;
|
||||
sender_address = Some(api.get_slatepack_address(mask1, 0)?.pub_key);
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard1);
|
||||
|
||||
let mut recipient_address = None;
|
||||
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
|
||||
// Receive wallet calls --receive=5
|
||||
let args = &mut ContractSetupArgsAPI {
|
||||
net_change: Some(5_000_000_000),
|
||||
..Default::default()
|
||||
};
|
||||
args.proof_args.sender_address = sender_address;
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
recipient_address = Some(api.get_slatepack_address(mask2, 0)?.pub_key);
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard2);
|
||||
|
||||
// Send wallet finalizes and posts
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
let args = &ContractSetupArgsAPI {
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard3);
|
||||
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
api.post_tx(m, &slate, false)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
assert!(wallet1_refreshed);
|
||||
assert_eq!(wallet1_info.last_confirmed_height, 13);
|
||||
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(txs.len(), 9);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// other account should be untouched
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
w.set_parent_key_id_by_name("account2")?;
|
||||
}
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?;
|
||||
assert_eq!(wallet1_info.last_confirmed_height, 12);
|
||||
let (_, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
assert_eq!(wallet1_info.last_confirmed_height, 13);
|
||||
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
println!("{:?}", txs);
|
||||
assert_eq!(txs.len(), 5);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// wallet 2 should only have this tx on the listener account
|
||||
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
|
||||
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
assert!(wallet2_refreshed);
|
||||
assert_eq!(wallet2_info.last_confirmed_height, 13);
|
||||
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(txs.len(), 1);
|
||||
Ok(())
|
||||
})?;
|
||||
// Default account on wallet 2 should be untouched
|
||||
{
|
||||
wallet_inst!(wallet2, w);
|
||||
w.set_parent_key_id_by_name("default")?;
|
||||
}
|
||||
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
|
||||
let (_, wallet2_info) = api.retrieve_summary_info(m, false, 1)?;
|
||||
assert_eq!(wallet2_info.last_confirmed_height, 0);
|
||||
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
assert!(wallet2_refreshed);
|
||||
assert_eq!(wallet2_info.last_confirmed_height, 13);
|
||||
assert_eq!(wallet2_info.total, 0,);
|
||||
assert_eq!(wallet2_info.amount_currently_spendable, 0,);
|
||||
// check tx log as well
|
||||
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(txs.len(), 0);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// let logging finish
|
||||
stopper.store(false, Ordering::Relaxed);
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_contract_accounts() -> Result<(), libwallet::Error> {
|
||||
let test_dir = "test_output/contract_accounts";
|
||||
setup(test_dir);
|
||||
contract_accounts_impl(test_dir)?;
|
||||
clean_output_dir(test_dir);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// Copyright 2022 The Grin Developers
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test a wallet doing contracts with different accounts and switching between them
|
||||
// #[macro_use]
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
extern crate log;
|
||||
|
||||
use grin_core as core;
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
use libwallet::contract::my_fee_contribution;
|
||||
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||
use libwallet::{Slate, SlateState};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallets, setup};
|
||||
|
||||
/// contract accounts testing when switching between accounts during transaction building
|
||||
fn contract_accounts_switch_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
// create two wallets with some extra accounts and don't mine anything in them
|
||||
let (wallets, _chain, stopper, _bh) = create_wallets(
|
||||
vec![
|
||||
vec![("default", 0), ("account1", 1), ("account2", 2)],
|
||||
vec![("default", 0), ("account1", 3), ("account2", 4)],
|
||||
],
|
||||
test_dir,
|
||||
)
|
||||
.unwrap();
|
||||
let wallet1 = wallets[0].0.clone();
|
||||
let mask1 = wallets[0].1.as_ref();
|
||||
let wallet2 = wallets[1].0.clone();
|
||||
let mask2 = wallets[1].1.as_ref();
|
||||
|
||||
let reward = core::consensus::REWARD;
|
||||
|
||||
// wallet1::account1 should have 1 in account1 (1 spendable)
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
w.set_parent_key_id_by_name("account1")?;
|
||||
}
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
assert!(wallet1_refreshed);
|
||||
assert_eq!(wallet1_info.last_confirmed_height, 10);
|
||||
assert_eq!(wallet1_info.total, 1 * reward);
|
||||
assert_eq!(wallet1_info.amount_currently_spendable, 1 * reward);
|
||||
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(txs.len(), 1);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// wallet1::account2 should have 2 in account1 (2 spendable)
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
w.set_parent_key_id_by_name("account2")?;
|
||||
}
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
// check last confirmed height on this account is different from above (should be 0)
|
||||
let (_, wallet1_info) = api.retrieve_summary_info(m, false, 1)?;
|
||||
assert_eq!(wallet1_info.last_confirmed_height, 3);
|
||||
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
assert!(wallet1_refreshed);
|
||||
assert_eq!(wallet1_info.last_confirmed_height, 10);
|
||||
assert_eq!(wallet1_info.total, 2 * reward);
|
||||
assert_eq!(wallet1_info.amount_currently_spendable, 2 * reward);
|
||||
// check tx log as well
|
||||
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(txs.len(), 2);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Make a transaction by sending 5 coins from wallet1::account1 -> wallet2::account2
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
w.set_parent_key_id_by_name("account1")?;
|
||||
}
|
||||
|
||||
let mut slate = Slate::blank(0, true); // this gets overriden below
|
||||
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
// Send wallet inititates a standard transaction with --send=5
|
||||
let args = &ContractNewArgsAPI {
|
||||
setup_args: ContractSetupArgsAPI {
|
||||
net_change: Some(-5_000_000_000),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_new(m, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard1);
|
||||
|
||||
// Receiver gets their coins on account2 where they can payjoin
|
||||
{
|
||||
wallet_inst!(wallet2, w);
|
||||
w.set_parent_key_id_by_name("account2")?;
|
||||
}
|
||||
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
|
||||
// Receive wallet calls --receive=5
|
||||
let args = &mut ContractSetupArgsAPI {
|
||||
net_change: Some(5_000_000_000),
|
||||
..Default::default()
|
||||
};
|
||||
args.proof_args.suppress_proof = true;
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard2);
|
||||
|
||||
// Switch account for wallet1 to account2 and finish the transaction (should use account1 to complete)
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
w.set_parent_key_id_by_name("account2")?;
|
||||
}
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
let args = &ContractSetupArgsAPI {
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard3);
|
||||
|
||||
// post tx and mine a block to wallet1::account2
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
api.post_tx(m, &slate, false)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// The currently set account (account2) should not be affected by this transaction because they weren't a part of it,
|
||||
// but it did mine a block and pick the transaction fees
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
assert!(wallet1_refreshed);
|
||||
assert_eq!(wallet1_info.last_confirmed_height, 11);
|
||||
assert_eq!(
|
||||
wallet1_info.total,
|
||||
3 * reward + core::libtx::tx_fee(2, 2, 1) // we have received a block reward and the tx fee (payjoin)
|
||||
);
|
||||
assert_eq!(wallet1_info.amount_currently_spendable, 2 * reward);
|
||||
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(txs.len(), 3);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Switch to wallet1::account1 and check that it sent 5 coins
|
||||
{
|
||||
wallet_inst!(wallet1, w);
|
||||
w.set_parent_key_id_by_name("account1")?;
|
||||
}
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
assert!(wallet1_refreshed);
|
||||
assert_eq!(wallet1_info.last_confirmed_height, 11);
|
||||
assert_eq!(
|
||||
wallet1_info.total,
|
||||
1 * reward - 5_000_000_000 - my_fee_contribution(1, 1, 1, 2)?.fee() // we subtract also our fee contribution
|
||||
);
|
||||
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(txs.len(), 2);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Switch to wallet2::account2 and check that it received 5 coins
|
||||
{
|
||||
wallet_inst!(wallet2, w);
|
||||
w.set_parent_key_id_by_name("account2")?;
|
||||
}
|
||||
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
|
||||
let (wallet2_refreshed, wallet2_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
assert!(wallet2_refreshed);
|
||||
assert_eq!(wallet2_info.last_confirmed_height, 11);
|
||||
assert_eq!(
|
||||
wallet2_info.total,
|
||||
4 * reward + 5_000_000_000 - my_fee_contribution(1, 1, 1, 2)?.fee() // we subtract also our fee contribution for a payjoin
|
||||
);
|
||||
let (_, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(txs.len(), 5);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// let logging finish
|
||||
stopper.store(false, Ordering::Relaxed);
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_contract_accounts_switch() -> Result<(), libwallet::Error> {
|
||||
let test_dir = "test_output/contract_accounts_switch";
|
||||
setup(test_dir);
|
||||
contract_accounts_switch_impl(test_dir)?;
|
||||
clean_output_dir(test_dir);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2022 The Grin Developers
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test a wallet doing contract early lock when using --add-outputs
|
||||
// #[macro_use]
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
extern crate log;
|
||||
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
|
||||
use grin_core::consensus;
|
||||
use libwallet::contract::my_fee_contribution;
|
||||
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||
use libwallet::{OutputStatus, Slate, SlateState};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallets, setup};
|
||||
|
||||
/// contract new with --add-outputs
|
||||
fn contract_early_lock_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
// create a single wallet and mine 5 blocks
|
||||
let (wallets, _chain, stopper, _bh) =
|
||||
create_wallets(vec![vec![("default", 5)]], test_dir).unwrap();
|
||||
let send_wallet = wallets[0].0.clone();
|
||||
let send_mask = wallets[0].1.as_ref();
|
||||
|
||||
// Confirm all our outputs are unspent
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let (_, commits) = api.retrieve_outputs(m, true, false, None)?;
|
||||
for commit in commits.iter() {
|
||||
assert_eq!(commit.output.status, OutputStatus::Unspent);
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let mut slate = Slate::blank(0, false); // this gets overriden below
|
||||
|
||||
// Call contract 'new' with --add-outputs
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
// Send wallet inititates a standard transaction with --send=80
|
||||
let args = &ContractNewArgsAPI {
|
||||
setup_args: ContractSetupArgsAPI {
|
||||
net_change: Some(-80_000_000_000),
|
||||
num_participants: 2,
|
||||
add_outputs: true,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_new(m, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard1);
|
||||
|
||||
// Assert we locked 2 inputs and prepared an unconfirmed change output
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let (_, commits) = api.retrieve_outputs(m, true, false, None)?;
|
||||
// we locked the first two coinbase outputs
|
||||
assert_eq!(commits[0].output.status, OutputStatus::Locked);
|
||||
assert_eq!(commits[1].output.status, OutputStatus::Locked);
|
||||
// we added a new unconfirmed change output
|
||||
let new_output_idx = commits.len() - 1;
|
||||
assert_eq!(
|
||||
commits[new_output_idx].output.status,
|
||||
OutputStatus::Unconfirmed
|
||||
);
|
||||
assert_eq!(
|
||||
commits[new_output_idx].output.value,
|
||||
2 * consensus::REWARD - 80_000_000_000 - my_fee_contribution(2, 1, 1, 2)?.fee()
|
||||
);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// let logging finish
|
||||
stopper.store(false, Ordering::Relaxed);
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_contract_early_lock_tx() -> Result<(), libwallet::Error> {
|
||||
let test_dir = "test_output/contract_early_lock_tx";
|
||||
setup(test_dir);
|
||||
contract_early_lock_tx_impl(test_dir)?;
|
||||
clean_output_dir(test_dir);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright 2023 The Grin Developers
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Development and testing of early payment proofs, restricted at the moment
|
||||
//! to contract-style transactions for experimental purposes
|
||||
//!
|
||||
//! https://github.com/mimblewimble/grin-rfcs/pull/70
|
||||
//!
|
||||
//!
|
||||
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
extern crate log;
|
||||
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
|
||||
use impls::test_framework::{self};
|
||||
use libwallet::contract::my_fee_contribution;
|
||||
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||
use libwallet::{Slate, SlateState, Slatepack, Slatepacker, SlatepackerArgs, TxLogEntryType};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallets, setup};
|
||||
|
||||
/// Development + Tests of early payment proof functionality - RSR workflow
|
||||
fn contract_early_proofs_rsr_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
// create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin)
|
||||
let (wallets, chain, stopper, mut bh) =
|
||||
create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap();
|
||||
let send_wallet = wallets[0].0.clone();
|
||||
let send_mask = wallets[0].1.as_ref();
|
||||
let recv_wallet = wallets[1].0.clone();
|
||||
let recv_mask = wallets[1].1.as_ref();
|
||||
|
||||
let mut slate = Slate::blank(0, true); // this gets overriden below
|
||||
|
||||
let mut sender_address = None;
|
||||
// Get sender address explicitly
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
sender_address = Some(api.get_slatepack_address(send_mask, 0)?.pub_key);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let mut recipient_address = None;
|
||||
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||
// Receive wallet (invoice) calls --receive=5
|
||||
let args = &mut ContractNewArgsAPI {
|
||||
setup_args: ContractSetupArgsAPI {
|
||||
net_change: Some(5_000_000_000),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
args.setup_args.proof_args.sender_address = sender_address;
|
||||
println!("SENDER ADDRESS: {:?}", sender_address);
|
||||
slate = api.contract_new(m, args)?;
|
||||
recipient_address = Some(api.get_slatepack_address(recv_mask, 0)?.pub_key);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
assert_eq!(slate.state, SlateState::Invoice1);
|
||||
println!("I1 State slate: {}", slate);
|
||||
|
||||
// Serialize slate into slatepack
|
||||
let slatepacker_args = SlatepackerArgs {
|
||||
sender: None,
|
||||
recipients: vec![],
|
||||
dec_key: None,
|
||||
};
|
||||
|
||||
let slate_packer = Slatepacker::new(slatepacker_args);
|
||||
let slate_packed = slate_packer.create_slatepack(&slate).unwrap();
|
||||
|
||||
let slate_unpacked = slate_packer.get_slate(&slate_packed).unwrap();
|
||||
println!("I2 Slate unpacked: {}", slate_unpacked);
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
// Sending wallet (invoice) signs
|
||||
let args = &ContractSetupArgsAPI {
|
||||
net_change: Some(-5_000_000_000),
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_sign(m, &slate_unpacked, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
println!("I2 State slate: {}", slate);
|
||||
|
||||
assert_eq!(slate.state, SlateState::Invoice2);
|
||||
|
||||
// Send wallet finalizes and posts
|
||||
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||
let args = &ContractSetupArgsAPI {
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Invoice3);
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
api.post_tx(m, &slate, false)?;
|
||||
Ok(())
|
||||
})?;
|
||||
bh += 1;
|
||||
|
||||
let _ =
|
||||
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
|
||||
bh += 3;
|
||||
|
||||
// Assert changes in receive wallet
|
||||
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len(), 5); // 4 mined and 1 received
|
||||
let tx_log = txs[4].clone();
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived);
|
||||
assert_eq!(tx_log.amount_credited, 5_000_000_000);
|
||||
assert_eq!(tx_log.amount_debited, 0);
|
||||
assert_eq!(tx_log.num_inputs, 1);
|
||||
assert_eq!(tx_log.num_outputs, 1);
|
||||
let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?);
|
||||
assert_eq!(tx_log.fee, expected_fees_paid);
|
||||
assert_eq!(
|
||||
wallet_info.amount_currently_spendable,
|
||||
4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid
|
||||
);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Assert changes in send wallet
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len() as u64, bh - 4 + 1); // send wallet didn't mine 4 blocks and made 1 tx
|
||||
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent);
|
||||
assert_eq!(tx_log.amount_credited, 0);
|
||||
assert_eq!(tx_log.amount_debited, 5_000_000_000);
|
||||
assert_eq!(tx_log.num_inputs, 1);
|
||||
assert_eq!(tx_log.num_outputs, 1);
|
||||
assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?));
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let mut invoice_proof = None;
|
||||
// Now some time has passed, sender retrieves and verify the payment proof
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, _m| {
|
||||
// Extract the stored data as an invoice proof
|
||||
invoice_proof =
|
||||
Some(api.retrieve_payment_proof_invoice(send_mask, true, None, Some(slate.id))?);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let invoice_proof = invoice_proof.unwrap();
|
||||
let invoice_proof_json = serde_json::to_string(&invoice_proof).unwrap();
|
||||
|
||||
// Should have all proof fields filled out
|
||||
println!("INVOICE PROOF: {}", invoice_proof_json);
|
||||
|
||||
wallet::controller::foreign_single_use(recv_wallet.clone(), recv_mask.cloned(), |api| {
|
||||
let mut proof = serde_json::from_str(&invoice_proof_json).unwrap();
|
||||
api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof)?;
|
||||
// tweak something and it shouldn't verify
|
||||
proof.amount = 400000;
|
||||
let retval = api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof);
|
||||
assert!(retval.is_err());
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// let logging finish
|
||||
stopper.store(false, Ordering::Relaxed);
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_early_proofs_rsr() -> Result<(), libwallet::Error> {
|
||||
let test_dir = "test_output/contract_early_proofs_rsr";
|
||||
setup(test_dir);
|
||||
contract_early_proofs_rsr_test_impl(test_dir)?;
|
||||
clean_output_dir(test_dir);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
// Copyright 2023 The Grin Developers
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Development and testing of early payment proofs, restricted at the moment
|
||||
//! to contract-style transactions for experimental purposes
|
||||
//!
|
||||
//! https://github.com/mimblewimble/grin-rfcs/pull/70
|
||||
//!
|
||||
//!
|
||||
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
extern crate log;
|
||||
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
|
||||
use impls::test_framework::{self};
|
||||
use libwallet::contract::my_fee_contribution;
|
||||
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||
use libwallet::{Slate, SlateState, TxLogEntryType};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallets, setup};
|
||||
|
||||
/// Development + Tests of early payment proof functionality
|
||||
fn contract_early_proofs_srs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
// create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin)
|
||||
let (wallets, chain, stopper, mut bh) =
|
||||
create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap();
|
||||
let send_wallet = wallets[0].0.clone();
|
||||
let send_mask = wallets[0].1.as_ref();
|
||||
let recv_wallet = wallets[1].0.clone();
|
||||
let recv_mask = wallets[1].1.as_ref();
|
||||
|
||||
let mut slate = Slate::blank(0, true); // this gets overriden below
|
||||
|
||||
let mut sender_address = None;
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
// Send wallet inititates a standard transaction with --send=5
|
||||
let args = &ContractNewArgsAPI {
|
||||
setup_args: ContractSetupArgsAPI {
|
||||
net_change: Some(-5_000_000_000),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_new(m, args)?;
|
||||
sender_address = Some(api.get_slatepack_address(send_mask, 0)?.pub_key);
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard1);
|
||||
|
||||
let mut recipient_address = None;
|
||||
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||
// Receive wallet calls --receive=5
|
||||
let args = &mut ContractSetupArgsAPI {
|
||||
net_change: Some(5_000_000_000),
|
||||
..Default::default()
|
||||
};
|
||||
// Note sender address explicity added here
|
||||
args.proof_args.sender_address = sender_address;
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
recipient_address = Some(api.get_slatepack_address(recv_mask, 0)?.pub_key);
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard2);
|
||||
|
||||
// Send wallet finalizes and posts
|
||||
//let mut sender_part_sig = None;
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let args = &ContractSetupArgsAPI {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard3);
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
api.post_tx(m, &slate, false)?;
|
||||
Ok(())
|
||||
})?;
|
||||
bh += 1;
|
||||
|
||||
let _ =
|
||||
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
|
||||
bh += 3;
|
||||
|
||||
// Assert changes in receive wallet
|
||||
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len(), 5); // 4 mined and 1 received
|
||||
let tx_log = txs[4].clone();
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived);
|
||||
assert_eq!(tx_log.amount_credited, 5_000_000_000);
|
||||
assert_eq!(tx_log.amount_debited, 0);
|
||||
assert_eq!(tx_log.num_inputs, 1);
|
||||
assert_eq!(tx_log.num_outputs, 1);
|
||||
let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?);
|
||||
assert_eq!(tx_log.fee, expected_fees_paid);
|
||||
assert_eq!(
|
||||
wallet_info.amount_currently_spendable,
|
||||
4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid
|
||||
);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Assert changes in send wallet
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len() as u64, bh - 4 + 1); // send wallet didn't mine 4 blocks and made 1 tx
|
||||
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent);
|
||||
assert_eq!(tx_log.amount_credited, 0);
|
||||
assert_eq!(tx_log.amount_debited, 5_000_000_000);
|
||||
assert_eq!(tx_log.num_inputs, 1);
|
||||
assert_eq!(tx_log.num_outputs, 1);
|
||||
assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?));
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let mut invoice_proof = None;
|
||||
// Now some time has passed, sender retrieves and verify the payment proof
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, _m| {
|
||||
// Extract the stored data as an invoice proof
|
||||
invoice_proof =
|
||||
Some(api.retrieve_payment_proof_invoice(send_mask, true, None, Some(slate.id))?);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let invoice_proof = invoice_proof.unwrap();
|
||||
let invoice_proof_json = serde_json::to_string(&invoice_proof).unwrap();
|
||||
|
||||
// Should have all proof fields filled out
|
||||
println!("INVOICE PROOF: {}", invoice_proof_json);
|
||||
|
||||
wallet::controller::foreign_single_use(recv_wallet.clone(), recv_mask.cloned(), |api| {
|
||||
let mut proof = serde_json::from_str(&invoice_proof_json).unwrap();
|
||||
api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof)?;
|
||||
// tweak something and it shouldn't verify
|
||||
proof.amount = 400000;
|
||||
let retval = api.verify_payment_proof_invoice(recipient_address.as_ref().unwrap(), &proof);
|
||||
assert!(retval.is_err());
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// let logging finish
|
||||
stopper.store(false, Ordering::Relaxed);
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_early_proofs_srs() -> Result<(), libwallet::Error> {
|
||||
let test_dir = "test_output/contract_early_proofs_src";
|
||||
setup(test_dir);
|
||||
contract_early_proofs_srs_test_impl(test_dir)?;
|
||||
clean_output_dir(test_dir);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright 2022 The Grin Developers
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test a wallet doing contract RSR flow
|
||||
// #[macro_use]
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
extern crate log;
|
||||
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
|
||||
use impls::test_framework::{self};
|
||||
use libwallet::contract::my_fee_contribution;
|
||||
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||
use libwallet::{Slate, SlateState, TxLogEntryType};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallets, setup};
|
||||
|
||||
/// contract RSR flow
|
||||
fn contract_rsr_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
// create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin)
|
||||
let (wallets, chain, stopper, mut bh) =
|
||||
create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap();
|
||||
let send_wallet = wallets[0].0.clone();
|
||||
let send_mask = wallets[0].1.as_ref();
|
||||
let recv_wallet = wallets[1].0.clone();
|
||||
let recv_mask = wallets[1].1.as_ref();
|
||||
|
||||
let mut slate = Slate::blank(0, true); // this gets overriden below
|
||||
|
||||
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||
// Receive wallet inititates an invoice transaction with --receive=5
|
||||
let args = &mut ContractNewArgsAPI {
|
||||
setup_args: ContractSetupArgsAPI {
|
||||
net_change: Some(5_000_000_000),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
args.setup_args.proof_args.suppress_proof = true;
|
||||
slate = api.contract_new(m, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Invoice1);
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
// Send Wallet calls --send=5
|
||||
let args = &ContractSetupArgsAPI {
|
||||
net_change: Some(-5_000_000_000),
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Invoice2);
|
||||
|
||||
// Receive wallet finalizes and posts
|
||||
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||
let args = &mut ContractSetupArgsAPI {
|
||||
..Default::default()
|
||||
};
|
||||
// TODO: This possibly shouldn't be needed here if no proof?
|
||||
args.proof_args.suppress_proof = true;
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Invoice3);
|
||||
|
||||
// Send wallet posts so receive wallet doesn't get the mined amount
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
api.post_tx(m, &slate, false)?;
|
||||
Ok(())
|
||||
})?;
|
||||
bh += 1;
|
||||
|
||||
let _ =
|
||||
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
|
||||
bh += 3;
|
||||
|
||||
// Assert changes in receive wallet
|
||||
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len(), 5); // 4 mined and 1 received
|
||||
let tx_log = txs[4].clone();
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived);
|
||||
assert_eq!(tx_log.amount_credited, 5_000_000_000);
|
||||
assert_eq!(tx_log.amount_debited, 0);
|
||||
assert_eq!(tx_log.num_inputs, 1);
|
||||
assert_eq!(tx_log.num_outputs, 1);
|
||||
let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?);
|
||||
assert_eq!(tx_log.fee, expected_fees_paid);
|
||||
assert_eq!(
|
||||
wallet_info.amount_currently_spendable,
|
||||
4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid
|
||||
);
|
||||
// println!("txlogentry: {:#?}", tx_log);
|
||||
// println!("wallet info: {:#?}", wallet_info);
|
||||
// let (validated, commits) = api.retrieve_outputs(m, true, false, Some(tx_log.id))?;
|
||||
// println!("commits: {:#?}", commits);
|
||||
// panic!("lala");
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Assert changes in send wallet
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len() as u64, bh - 4 + 1); // send_wallet didn't mine 4 blocks and made 1 tx
|
||||
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent);
|
||||
assert_eq!(tx_log.amount_credited, 0);
|
||||
assert_eq!(tx_log.amount_debited, 5_000_000_000);
|
||||
assert_eq!(tx_log.num_inputs, 1);
|
||||
assert_eq!(tx_log.num_outputs, 1);
|
||||
assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?));
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// let logging finish
|
||||
stopper.store(false, Ordering::Relaxed);
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_contract_rsr_tx() -> Result<(), libwallet::Error> {
|
||||
let test_dir = "test_output/contract_rsr_tx";
|
||||
setup(test_dir);
|
||||
contract_rsr_tx_impl(test_dir)?;
|
||||
clean_output_dir(test_dir);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2022 The Grin Developers
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test a wallet doing contract self-spend flow
|
||||
// #[macro_use]
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
extern crate log;
|
||||
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
|
||||
use impls::test_framework::{self};
|
||||
use libwallet::contract::my_fee_contribution;
|
||||
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||
use libwallet::{Slate, SlateState, TxLogEntryType};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallets, setup};
|
||||
|
||||
/// contract self-spend flow
|
||||
fn contract_self_spend_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
// create a single wallet and mine 4 blocks
|
||||
let (wallets, chain, stopper, mut bh) =
|
||||
create_wallets(vec![vec![("default", 4)]], test_dir).unwrap();
|
||||
let send_wallet = wallets[0].0.clone();
|
||||
let send_mask = wallets[0].1.as_ref();
|
||||
|
||||
let mut slate = Slate::blank(0, true); // this gets overriden below
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
// Send wallet inititates a standard transaction with --send=0
|
||||
let args = &ContractNewArgsAPI {
|
||||
setup_args: ContractSetupArgsAPI {
|
||||
net_change: Some(0),
|
||||
num_participants: 1,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_new(m, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard1);
|
||||
|
||||
// Send wallet finalizes and posts
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let args = &ContractSetupArgsAPI {
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
// In the case of a self-spend, we just finish the slate when it's in the Standard2 state
|
||||
assert_eq!(slate.state, SlateState::Standard2);
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
api.post_tx(m, &slate, false)?;
|
||||
Ok(())
|
||||
})?;
|
||||
bh += 1;
|
||||
|
||||
let _ =
|
||||
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
|
||||
bh += 3;
|
||||
|
||||
// Assert changes in send wallet
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len() as u64, bh + 1); // send wallet didn't mine 4 blocks and made 1 tx
|
||||
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSelfSpend);
|
||||
assert_eq!(tx_log.amount_credited, 0);
|
||||
assert_eq!(tx_log.amount_debited, 0);
|
||||
assert_eq!(tx_log.num_inputs, 1);
|
||||
assert_eq!(tx_log.num_outputs, 1);
|
||||
assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 1)?));
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// let logging finish
|
||||
stopper.store(false, Ordering::Relaxed);
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_contract_self_spend_tx() -> Result<(), libwallet::Error> {
|
||||
let test_dir = "test_output/contract_self_spend_tx";
|
||||
setup(test_dir);
|
||||
contract_self_spend_tx_impl(test_dir)?;
|
||||
clean_output_dir(test_dir);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2022 The Grin Developers
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test a wallet doing contract self-spend flow
|
||||
// #[macro_use]
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
extern crate log;
|
||||
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
|
||||
use impls::test_framework::{self};
|
||||
use libwallet::contract::my_fee_contribution;
|
||||
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||
use libwallet::{Slate, SlateState, TxLogEntryType};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallets, setup};
|
||||
|
||||
/// contract self-spend flow
|
||||
fn contract_self_spend_cancel_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
// create a single wallet and mine 4 blocks
|
||||
let (wallets, chain, stopper, mut bh) =
|
||||
create_wallets(vec![vec![("default", 4)]], test_dir).unwrap();
|
||||
let send_wallet = wallets[0].0.clone();
|
||||
let send_mask = wallets[0].1.as_ref();
|
||||
|
||||
let mut slate = Slate::blank(0, true); // this gets overriden below
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
// Send wallet initiates a standard transaction with --send=0
|
||||
let args = &ContractNewArgsAPI {
|
||||
setup_args: ContractSetupArgsAPI {
|
||||
net_change: Some(0),
|
||||
num_participants: 1,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_new(m, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard1);
|
||||
|
||||
// Send wallet finalizes and posts
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
api.cancel_tx(m, None, Some(slate.id))?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Assert tx log has been cancelled
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let query_args = libwallet::RetrieveTxQueryArgs {
|
||||
exclude_cancelled: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, Some(query_args))?;
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len() as u64, 5); // send wallet didn't mine 4 blocks and made 1 tx
|
||||
let tx_log = txs[4].clone(); // TODO: why -5 and not -4?
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSelfSpendCancelled);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// let logging finish
|
||||
stopper.store(false, Ordering::Relaxed);
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_contract_self_spend_cancel() -> Result<(), libwallet::Error> {
|
||||
let test_dir = "test_output/contract_self_spend_cancel";
|
||||
setup(test_dir);
|
||||
contract_self_spend_cancel_impl(test_dir)?;
|
||||
clean_output_dir(test_dir);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright 2022 The Grin Developers
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test a wallet doing contract self-spend flow by using custom inputs and creating custom outputs
|
||||
// #[macro_use]
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
extern crate log;
|
||||
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
|
||||
use grin_core::consensus;
|
||||
use impls::test_framework::{self};
|
||||
use libwallet::contract::my_fee_contribution;
|
||||
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI, OutputSelectionArgs};
|
||||
use libwallet::{OutputStatus, Slate, SlateState, TxLogEntryType};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallets, setup};
|
||||
|
||||
/// contract self-spend flow with custom picked inputs and outputs
|
||||
fn contract_self_spend_custom_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
// create a single wallet and mine 4 blocks
|
||||
let (wallets, chain, stopper, mut bh) =
|
||||
create_wallets(vec![vec![("default", 10)]], test_dir).unwrap();
|
||||
let send_wallet = wallets[0].0.clone();
|
||||
let send_mask = wallets[0].1.as_ref();
|
||||
|
||||
let mut use_inputs = String::from("");
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let (_, commits) = api.retrieve_outputs(m, true, false, None)?;
|
||||
println!("OOOT: {:?}", commits[0].output);
|
||||
use_inputs = format!(
|
||||
"{},{}",
|
||||
commits[0].output.commit.as_ref().unwrap(),
|
||||
commits[1].output.commit.as_ref().unwrap()
|
||||
);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let mut slate = Slate::blank(0, true); // this gets overriden below
|
||||
|
||||
let selection_args = OutputSelectionArgs {
|
||||
min_input_confirmation: 0,
|
||||
use_inputs: Some(use_inputs.clone()), // we will use two coinbase inputs
|
||||
make_outputs: Some(String::from("88,35,3,0.2,15")), // the sum is such that it will need to pick another input making total of 3 inputs
|
||||
};
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
// Send wallet inititates a standard transaction with --send=0
|
||||
let args = &ContractNewArgsAPI {
|
||||
setup_args: ContractSetupArgsAPI {
|
||||
net_change: Some(0),
|
||||
num_participants: 1,
|
||||
selection_args: selection_args.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_new(m, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard1);
|
||||
|
||||
// Send wallet finalizes and posts
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let args = &ContractSetupArgsAPI {
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
// In the case of a self-spend, we just finish the slate when it's in the Standard2 state
|
||||
assert_eq!(slate.state, SlateState::Standard2);
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
api.post_tx(m, &slate, false)?;
|
||||
Ok(())
|
||||
})?;
|
||||
bh += 1;
|
||||
|
||||
let _ =
|
||||
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
|
||||
bh += 3;
|
||||
|
||||
// Assert changes in send wallet
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len() as u64, bh + 1); // send wallet didn't mine 4 blocks and made 1 tx
|
||||
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSelfSpend);
|
||||
assert_eq!(tx_log.amount_credited, 0);
|
||||
assert_eq!(tx_log.amount_debited, 0);
|
||||
assert_eq!(tx_log.num_inputs, 3);
|
||||
assert_eq!(tx_log.num_outputs, 6);
|
||||
assert_eq!(tx_log.fee, Some(my_fee_contribution(3, 6, 1, 1)?));
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let (_, commits) = api.retrieve_outputs(m, true, false, None)?;
|
||||
// Assert used inputs are the ones we specified
|
||||
let used_inputs = use_inputs.split(",").collect::<Vec<&str>>();
|
||||
assert_eq!(commits[0].output.status, OutputStatus::Spent);
|
||||
assert_eq!(commits[0].output.commit.as_ref().unwrap(), used_inputs[0]);
|
||||
assert_eq!(commits[1].output.status, OutputStatus::Spent);
|
||||
assert_eq!(commits[1].output.commit.as_ref().unwrap(), used_inputs[1]);
|
||||
assert_eq!(commits[2].output.status, OutputStatus::Spent);
|
||||
// Assert expected outputs were created
|
||||
// 88, 35, 3, 0.2, 15 and a change output
|
||||
assert_eq!(commits[10].output.value, 88 * consensus::GRIN_BASE);
|
||||
assert_eq!(commits[11].output.value, 35 * consensus::GRIN_BASE);
|
||||
assert_eq!(commits[12].output.value, 3 * consensus::GRIN_BASE);
|
||||
assert_eq!(
|
||||
commits[13].output.value,
|
||||
(0.2 * consensus::GRIN_BASE as f64) as u64
|
||||
);
|
||||
assert_eq!(commits[14].output.value, 15 * consensus::GRIN_BASE);
|
||||
// change output is 3*reward - (88-35-3-0.2-15) - my_fees
|
||||
assert_eq!(
|
||||
commits[15].output.value,
|
||||
3 * consensus::REWARD
|
||||
- selection_args.sum_output_amounts()
|
||||
- my_fee_contribution(3, 6, 1, 1)?.fee()
|
||||
);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// let logging finish
|
||||
stopper.store(false, Ordering::Relaxed);
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_contract_self_spend_custom_tx() -> Result<(), libwallet::Error> {
|
||||
let test_dir = "test_output/contract_self_spend_custom_tx";
|
||||
setup(test_dir);
|
||||
contract_self_spend_custom_tx_impl(test_dir)?;
|
||||
clean_output_dir(test_dir);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright 2022 The Grin Developers
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test a wallet doing contract SRS flow
|
||||
// #[macro_use]
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
extern crate log;
|
||||
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
|
||||
use impls::test_framework::{self};
|
||||
use libwallet::contract::my_fee_contribution;
|
||||
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||
use libwallet::{Slate, SlateState, TxLogEntryType};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallets, setup};
|
||||
|
||||
/// contract SRS flow
|
||||
fn contract_srs_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
// create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin)
|
||||
let (wallets, chain, stopper, mut bh) =
|
||||
create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap();
|
||||
let send_wallet = wallets[0].0.clone();
|
||||
let send_mask = wallets[0].1.as_ref();
|
||||
let recv_wallet = wallets[1].0.clone();
|
||||
let recv_mask = wallets[1].1.as_ref();
|
||||
|
||||
let mut slate = Slate::blank(0, true); // this gets overriden below
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
// Send wallet inititates a standard transaction with --send=5
|
||||
let args = &mut ContractNewArgsAPI {
|
||||
setup_args: ContractSetupArgsAPI {
|
||||
net_change: Some(-5_000_000_000),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_new(m, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard1);
|
||||
|
||||
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||
// Receive wallet calls --receive=5
|
||||
let args = &mut ContractSetupArgsAPI {
|
||||
net_change: Some(5_000_000_000),
|
||||
..Default::default()
|
||||
};
|
||||
args.proof_args.suppress_proof = true;
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard2);
|
||||
|
||||
// Send wallet finalizes and posts
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let args = &mut ContractSetupArgsAPI {
|
||||
..Default::default()
|
||||
};
|
||||
args.proof_args.suppress_proof = true;
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard3);
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
api.post_tx(m, &slate, false)?;
|
||||
Ok(())
|
||||
})?;
|
||||
bh += 1;
|
||||
|
||||
let _ =
|
||||
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
|
||||
bh += 3;
|
||||
|
||||
// Assert changes in receive wallet
|
||||
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len(), 5); // 4 mined and 1 received
|
||||
let tx_log = txs[4].clone();
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived);
|
||||
assert_eq!(tx_log.amount_credited, 5_000_000_000);
|
||||
assert_eq!(tx_log.amount_debited, 0);
|
||||
assert_eq!(tx_log.num_inputs, 1);
|
||||
assert_eq!(tx_log.num_outputs, 1);
|
||||
let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?);
|
||||
assert_eq!(tx_log.fee, expected_fees_paid);
|
||||
assert_eq!(
|
||||
wallet_info.amount_currently_spendable,
|
||||
4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid
|
||||
);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Assert changes in send wallet
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len() as u64, bh - 4 + 1); // send wallet didn't mine 4 blocks and made 1 tx
|
||||
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent);
|
||||
assert_eq!(tx_log.amount_credited, 0);
|
||||
assert_eq!(tx_log.amount_debited, 5_000_000_000);
|
||||
assert_eq!(tx_log.num_inputs, 1);
|
||||
assert_eq!(tx_log.num_outputs, 1);
|
||||
assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?));
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// let logging finish
|
||||
stopper.store(false, Ordering::Relaxed);
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_contract_srs_tx() -> Result<(), libwallet::Error> {
|
||||
let test_dir = "test_output/contract_srs_tx";
|
||||
setup(test_dir);
|
||||
contract_srs_tx_impl(test_dir)?;
|
||||
clean_output_dir(test_dir);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright 2022 The Grin Developers
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test a wallet doing contract SRS flow
|
||||
// #[macro_use]
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
extern crate log;
|
||||
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
|
||||
use impls::test_framework::{self};
|
||||
use libwallet::contract::my_fee_contribution;
|
||||
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||
use libwallet::mwmixnet::onion::crypto::secp;
|
||||
use libwallet::mwmixnet::types::MixnetReqCreationParams;
|
||||
use libwallet::{Slate, SlateState, TxLogEntryType};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallets, setup};
|
||||
|
||||
/// contract SRS flow - just creating an mwmixnet tx at the moment
|
||||
fn contract_srs_mwmixnet_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
// create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin)
|
||||
let (wallets, chain, stopper, mut bh) =
|
||||
create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap();
|
||||
let send_wallet = wallets[0].0.clone();
|
||||
let send_mask = wallets[0].1.as_ref();
|
||||
let recv_wallet = wallets[1].0.clone();
|
||||
let recv_mask = wallets[1].1.as_ref();
|
||||
|
||||
let mut slate = Slate::blank(0, true); // this gets overriden below
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
// Send wallet inititates a standard transaction with --send=5
|
||||
let args = &mut ContractNewArgsAPI {
|
||||
setup_args: ContractSetupArgsAPI {
|
||||
net_change: Some(-5_000_000_000),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_new(m, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard1);
|
||||
|
||||
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||
// Receive wallet calls --receive=5
|
||||
let args = &mut ContractSetupArgsAPI {
|
||||
net_change: Some(5_000_000_000),
|
||||
..Default::default()
|
||||
};
|
||||
args.proof_args.suppress_proof = true;
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard2);
|
||||
|
||||
// Send wallet finalizes and posts
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let args = &mut ContractSetupArgsAPI {
|
||||
..Default::default()
|
||||
};
|
||||
args.proof_args.suppress_proof = true;
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard3);
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let server_key_1 = secp::random_secret();
|
||||
let server_key_2 = secp::random_secret();
|
||||
let params = MixnetReqCreationParams {
|
||||
server_keys: vec![server_key_1, server_key_2],
|
||||
fee_per_hop: 50_000_000,
|
||||
};
|
||||
//api.create_mwmixnet_req(send_mask, ¶ms, &slate)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
bh += 1;
|
||||
|
||||
/*
|
||||
let _ =
|
||||
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
|
||||
bh += 3;
|
||||
|
||||
// Assert changes in receive wallet
|
||||
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len(), 5); // 4 mined and 1 received
|
||||
let tx_log = txs[4].clone();
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived);
|
||||
assert_eq!(tx_log.amount_credited, 5_000_000_000);
|
||||
assert_eq!(tx_log.amount_debited, 0);
|
||||
assert_eq!(tx_log.num_inputs, 1);
|
||||
assert_eq!(tx_log.num_outputs, 1);
|
||||
let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?);
|
||||
assert_eq!(tx_log.fee, expected_fees_paid);
|
||||
assert_eq!(
|
||||
wallet_info.amount_currently_spendable,
|
||||
4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid
|
||||
);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Assert changes in send wallet
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len() as u64, bh - 4 + 1); // send wallet didn't mine 4 blocks and made 1 tx
|
||||
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent);
|
||||
assert_eq!(tx_log.amount_credited, 0);
|
||||
assert_eq!(tx_log.amount_debited, 5_000_000_000);
|
||||
assert_eq!(tx_log.num_inputs, 1);
|
||||
assert_eq!(tx_log.num_outputs, 1);
|
||||
assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?));
|
||||
Ok(())
|
||||
})?;*/
|
||||
|
||||
// let logging finish
|
||||
stopper.store(false, Ordering::Relaxed);
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_contract_srs_mwmixnet_tx() -> Result<(), libwallet::Error> {
|
||||
let test_dir = "test_output/contract_srs_mwmixnet_tx";
|
||||
setup(test_dir);
|
||||
contract_srs_mwmixnet_tx_impl(test_dir)?;
|
||||
clean_output_dir(test_dir);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright 2021 The Grin Developers
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test a wallet sending to self
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
|
||||
use grin_core as core;
|
||||
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
use impls::test_framework::{self, LocalWalletClient};
|
||||
use libwallet::{InitTxArgs, RetrieveTxQueryArgs};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallet_proxy, setup};
|
||||
|
||||
/// self send impl
|
||||
fn self_send_cancel_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
// Create a new proxy to simulate server and wallet responses
|
||||
let mut wallet_proxy = create_wallet_proxy(test_dir);
|
||||
let chain = wallet_proxy.chain.clone();
|
||||
let stopper = wallet_proxy.running.clone();
|
||||
|
||||
// Create a new wallet test client, and set its queues to communicate with the
|
||||
// proxy
|
||||
create_wallet_and_add!(
|
||||
client1,
|
||||
wallet1,
|
||||
mask1_i,
|
||||
test_dir,
|
||||
"wallet1",
|
||||
None,
|
||||
&mut wallet_proxy,
|
||||
true
|
||||
);
|
||||
let mask1 = (&mask1_i).as_ref();
|
||||
|
||||
// Set the wallet proxy listener running
|
||||
thread::spawn(move || {
|
||||
if let Err(e) = wallet_proxy.run() {
|
||||
error!("Wallet Proxy error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
// few values to keep things shorter
|
||||
let reward = core::consensus::REWARD;
|
||||
|
||||
let mut bh = 10u64;
|
||||
let _ =
|
||||
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
|
||||
|
||||
// Should have 5 in account1 (5 spendable), 5 in account (2 spendable)
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
let (wallet1_refreshed, wallet1_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
assert!(wallet1_refreshed);
|
||||
assert_eq!(wallet1_info.last_confirmed_height, bh);
|
||||
assert_eq!(wallet1_info.total, bh * reward);
|
||||
// send to send
|
||||
let args = InitTxArgs {
|
||||
src_acct_name: None,
|
||||
amount: reward * 2,
|
||||
minimum_confirmations: 2,
|
||||
max_outputs: 500,
|
||||
num_change_outputs: 1,
|
||||
selection_strategy_is_use_all: true,
|
||||
..Default::default()
|
||||
};
|
||||
let mut slate = api.init_send_tx(m, args)?;
|
||||
api.tx_lock_outputs(m, &slate)?;
|
||||
// Send directly to self
|
||||
wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| {
|
||||
slate = api.receive_tx(&slate, None, None)?;
|
||||
Ok(())
|
||||
})?;
|
||||
// Now cancel the transaction
|
||||
api.cancel_tx(mask1, None, Some(slate.id.clone()))?;
|
||||
bh += 1;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
|
||||
let query = RetrieveTxQueryArgs {
|
||||
include_outstanding_only: Some(true),
|
||||
exclude_cancelled: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
let txs = api.retrieve_txs(mask1, true, None, None, Some(query))?;
|
||||
for tx in txs.1.iter() {
|
||||
println!("Tx: {:?}", tx);
|
||||
}
|
||||
assert!(txs.1.is_empty());
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// let logging finish
|
||||
stopper.store(false, Ordering::Relaxed);
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_self_send_cancel() {
|
||||
let test_dir = "test_output/self_send_cancel";
|
||||
setup(test_dir);
|
||||
if let Err(e) = self_send_cancel_test_impl(test_dir) {
|
||||
panic!("Libwallet Error: {}", e);
|
||||
}
|
||||
clean_output_dir(test_dir);
|
||||
}
|
||||
Reference in New Issue
Block a user