mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-17 15:21:39 +02:00
validate contract net change
This commit is contained in:
@@ -69,6 +69,18 @@ fn contract_rsr_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error>
|
|||||||
send_mask,
|
send_mask,
|
||||||
PathBuf::from(test_dir),
|
PathBuf::from(test_dir),
|
||||||
|api, m| {
|
|api, m| {
|
||||||
|
let wrong_args = ContractSetupArgsAPI {
|
||||||
|
selection_args: common::contract_selection_args(),
|
||||||
|
net_change: Some(5_000_000_000),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let err = api.contract_sign(m, &slate, &wrong_args).unwrap_err();
|
||||||
|
assert!(matches!(
|
||||||
|
err,
|
||||||
|
libwallet::Error::GenericError(ref msg)
|
||||||
|
if msg == "Expected net change -5000000000, got 5000000000 (did you mean --send instead of --receive?)"
|
||||||
|
));
|
||||||
|
|
||||||
// Send Wallet calls --send=5
|
// Send Wallet calls --send=5
|
||||||
let args = &ContractSetupArgsAPI {
|
let args = &ContractSetupArgsAPI {
|
||||||
selection_args: common::contract_selection_args(),
|
selection_args: common::contract_selection_args(),
|
||||||
|
|||||||
@@ -70,6 +70,18 @@ fn contract_srs_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error>
|
|||||||
recv_mask,
|
recv_mask,
|
||||||
PathBuf::from(test_dir),
|
PathBuf::from(test_dir),
|
||||||
|api, m| {
|
|api, m| {
|
||||||
|
let wrong_args = ContractSetupArgsAPI {
|
||||||
|
selection_args: common::contract_selection_args(),
|
||||||
|
net_change: Some(-5_000_000_000),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let err = api.contract_sign(m, &slate, &wrong_args).unwrap_err();
|
||||||
|
assert!(matches!(
|
||||||
|
err,
|
||||||
|
libwallet::Error::GenericError(ref msg)
|
||||||
|
if msg == "Expected net change 5000000000, got -5000000000 (did you mean --receive instead of --send?)"
|
||||||
|
));
|
||||||
|
|
||||||
// Receive wallet calls --receive=5
|
// Receive wallet calls --receive=5
|
||||||
let args = &mut ContractSetupArgsAPI {
|
let args = &mut ContractSetupArgsAPI {
|
||||||
selection_args: common::contract_selection_args(),
|
selection_args: common::contract_selection_args(),
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ Separating side effects until the 'save_step' part would make these functions mu
|
|||||||
clearer, for the API as much as the CLI.
|
clearer, for the API as much as the CLI.
|
||||||
- `new` has no `--no-setup`.
|
- `new` has no `--no-setup`.
|
||||||
- Add custom fee rates and contract TTLs, or document them as unsupported.
|
- Add custom fee rates and contract TTLs, or document them as unsupported.
|
||||||
- Detect a reversed `--send` or `--receive` when signing a slate for the first time.
|
|
||||||
- Let `view` report whether a slatepack was encrypted for this wallet.
|
- Let `view` report whether a slatepack was encrypted for this wallet.
|
||||||
- Show all inputs, outputs, fees and resulting balance changes before the CLI signs.
|
- Show all inputs, outputs, fees and resulting balance changes before the CLI signs.
|
||||||
- Distinguish the transfer amount from the fee-adjusted balance change in `view`.
|
- Distinguish the transfer amount from the fee-adjusted balance change in `view`.
|
||||||
|
|||||||
@@ -20,8 +20,23 @@ mod setup;
|
|||||||
mod sign;
|
mod sign;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::slate::SlateState;
|
||||||
|
|
||||||
pub use self::new::new;
|
pub use self::new::new;
|
||||||
pub use self::revoke::revoke;
|
pub use self::revoke::revoke;
|
||||||
pub use self::setup::setup;
|
pub use self::setup::setup;
|
||||||
pub use self::sign::sign;
|
pub use self::sign::sign;
|
||||||
pub use self::view::view;
|
pub use self::view::view;
|
||||||
|
|
||||||
|
// Net change expected from the other party at the first step
|
||||||
|
fn initial_net_change(state: &SlateState, amount: u64) -> Result<Option<i64>, Error> {
|
||||||
|
let sign = match state {
|
||||||
|
SlateState::Standard1 => 1,
|
||||||
|
SlateState::Invoice1 => -1,
|
||||||
|
_ => return Ok(None),
|
||||||
|
};
|
||||||
|
let amount = i64::try_from(amount)
|
||||||
|
.map_err(|_| Error::GenericError(format!("Slate amount {} exceeds i64", amount)))?;
|
||||||
|
Ok(Some(sign * amount))
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
//! Implementation of contract sign
|
//! Implementation of contract sign
|
||||||
|
|
||||||
|
use super::initial_net_change;
|
||||||
use crate::backend::WalletBackend;
|
use crate::backend::WalletBackend;
|
||||||
use crate::contract;
|
use crate::contract;
|
||||||
use crate::contract::actions::setup;
|
use crate::contract::actions::setup;
|
||||||
@@ -21,9 +22,31 @@ use crate::contract::types::ContractSetupArgsAPI;
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::grin_keychain::Keychain;
|
use crate::grin_keychain::Keychain;
|
||||||
use crate::grin_util::secp::key::SecretKey;
|
use crate::grin_util::secp::key::SecretKey;
|
||||||
use crate::slate::Slate;
|
use crate::slate::{Slate, SlateState};
|
||||||
use crate::types::{Context, NodeClient};
|
use crate::types::{Context, NodeClient};
|
||||||
|
|
||||||
|
fn verify_first_sign_net_change(
|
||||||
|
state: &SlateState,
|
||||||
|
amount: u64,
|
||||||
|
net_change: i64,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if let Some(expected) = initial_net_change(state, amount)? {
|
||||||
|
if net_change != expected {
|
||||||
|
let reversed = expected.checked_neg() == Some(net_change);
|
||||||
|
let hint = match state {
|
||||||
|
SlateState::Standard1 if reversed => " (did you mean --receive instead of --send?)",
|
||||||
|
SlateState::Invoice1 if reversed => " (did you mean --send instead of --receive?)",
|
||||||
|
_ => "",
|
||||||
|
};
|
||||||
|
return Err(Error::GenericError(format!(
|
||||||
|
"Expected net change {}, got {}{}",
|
||||||
|
expected, net_change, hint
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Sign a contract
|
/// Sign a contract
|
||||||
pub fn sign<C, K>(
|
pub fn sign<C, K>(
|
||||||
w: &mut WalletBackend<C, K>,
|
w: &mut WalletBackend<C, K>,
|
||||||
@@ -79,6 +102,9 @@ where
|
|||||||
// Ensure net_change has been provided
|
// Ensure net_change has been provided
|
||||||
let expected_net_change =
|
let expected_net_change =
|
||||||
contract::utils::get_net_change(existing_context, setup_args.net_change)?;
|
contract::utils::get_net_change(existing_context, setup_args.net_change)?;
|
||||||
|
if existing_context.is_none() {
|
||||||
|
verify_first_sign_net_change(&sl.state, sl.amount, expected_net_change)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Define the values that must be provided in the setup phase at the sign step
|
// Define the values that must be provided in the setup phase at the sign step
|
||||||
let mut setup_args = setup_args.clone();
|
let mut setup_args = setup_args.clone();
|
||||||
@@ -104,3 +130,37 @@ where
|
|||||||
|
|
||||||
Ok((sl, context))
|
Ok((sl, context))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn first_sign_net_change() {
|
||||||
|
for (state, amount, net_change) in [
|
||||||
|
(SlateState::Standard1, 10, 10),
|
||||||
|
(SlateState::Invoice1, 10, -10),
|
||||||
|
(SlateState::Standard1, 0, 0),
|
||||||
|
(SlateState::Invoice1, 0, 0),
|
||||||
|
(SlateState::Standard2, 10, -10),
|
||||||
|
(SlateState::Invoice2, 10, 10),
|
||||||
|
(SlateState::Invoice3, 10, 10),
|
||||||
|
] {
|
||||||
|
assert!(verify_first_sign_net_change(&state, amount, net_change).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (state, amount, net_change) in [
|
||||||
|
(SlateState::Standard1, 10, -10),
|
||||||
|
(SlateState::Invoice1, 10, 10),
|
||||||
|
(SlateState::Invoice1, 10, -9),
|
||||||
|
] {
|
||||||
|
assert!(verify_first_sign_net_change(&state, amount, net_change).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
let err = verify_first_sign_net_change(&SlateState::Standard1, 10, 9).unwrap_err();
|
||||||
|
assert!(matches!(
|
||||||
|
err,
|
||||||
|
Error::GenericError(ref msg) if msg == "Expected net change 10, got 9"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
//! Implementation of contract view
|
//! Implementation of contract view
|
||||||
|
|
||||||
|
use super::initial_net_change;
|
||||||
use crate::backend::WalletBackend;
|
use crate::backend::WalletBackend;
|
||||||
use crate::contract;
|
use crate::contract;
|
||||||
use crate::contract::types::{ContractView, OwnCommitmentStatus};
|
use crate::contract::types::{ContractView, OwnCommitmentStatus};
|
||||||
@@ -24,17 +25,6 @@ use crate::internal::updater;
|
|||||||
use crate::slate::{Slate, SlateState};
|
use crate::slate::{Slate, SlateState};
|
||||||
use crate::types::NodeClient;
|
use crate::types::NodeClient;
|
||||||
|
|
||||||
fn suggested_net_change(state: &SlateState, amount: u64) -> Result<Option<i64>, Error> {
|
|
||||||
let sign = match state {
|
|
||||||
SlateState::Standard1 => 1,
|
|
||||||
SlateState::Invoice1 => -1,
|
|
||||||
_ => return Ok(None),
|
|
||||||
};
|
|
||||||
let amount = i64::try_from(amount)
|
|
||||||
.map_err(|_| Error::GenericError(format!("Slate amount {} exceeds i64", amount)))?;
|
|
||||||
Ok(Some(sign * amount))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// View contract
|
/// View contract
|
||||||
pub fn view<C, K>(
|
pub fn view<C, K>(
|
||||||
w: &mut WalletBackend<C, K>,
|
w: &mut WalletBackend<C, K>,
|
||||||
@@ -69,7 +59,7 @@ where
|
|||||||
Err(Error::NotFoundErr(_)) => None,
|
Err(Error::NotFoundErr(_)) => None,
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
};
|
||||||
let suggested_net_change = suggested_net_change(&slate.state, slate.amount)?;
|
let suggested_net_change = initial_net_change(&slate.state, slate.amount)?;
|
||||||
// A contract is executed once the transaction it produced has confirmed. That is
|
// A contract is executed once the transaction it produced has confirmed. That is
|
||||||
// recorded on our own tx log entry for this slate, so no chain lookup is needed; a
|
// recorded on our own tx log entry for this slate, so no chain lookup is needed; a
|
||||||
// slate we have never signed simply has no entry and is not executed.
|
// slate we have never signed simply has no entry and is not executed.
|
||||||
@@ -125,15 +115,15 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn suggested_change_direction() {
|
fn suggested_change_direction() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
suggested_net_change(&SlateState::Standard1, 10).unwrap(),
|
initial_net_change(&SlateState::Standard1, 10).unwrap(),
|
||||||
Some(10)
|
Some(10)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
suggested_net_change(&SlateState::Invoice1, 10).unwrap(),
|
initial_net_change(&SlateState::Invoice1, 10).unwrap(),
|
||||||
Some(-10)
|
Some(-10)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
suggested_net_change(&SlateState::Standard2, 10).unwrap(),
|
initial_net_change(&SlateState::Standard2, 10).unwrap(),
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user