Skip to content

Commit

Permalink
Merge pull request #2749 from dusk-network/2396/balance-check
Browse files Browse the repository at this point in the history
rusk-wallet: Add balance check validation
  • Loading branch information
HDauven authored Oct 29, 2024
2 parents 54cffca + 04da891 commit 3c09734
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 60 deletions.
5 changes: 5 additions & 0 deletions rusk-wallet/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add contract deploy and contract calling [#2402]
- Add support for RUES [#2401]
- Add Moonlight stake, unstake and withdraw [#2400]
- Add balance validation for any given transaction action [#2396]
- Add Moonlight-Phoenix conversion [#2340]
- Add Moonlight transactions [#2288]

### Changed

- Changed default gas limits
- Split `prove_and_propagate` into `prove` and `propagate` [#2708]
- Unify `sndr_idx` and `profile_idx` fields in `Command` enum [#2702]
- Rename `--profile` flag to `--wallet-dir` [#2682]
Expand All @@ -27,8 +29,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fix

- Fix stake info for inactive stakes with rewards [#2766]
- Fix Moonlight stake reward withdrawal [#2523]

[#2766]: https://github.com/dusk-network/rusk/issues/2766
[#2708]: https://github.com/dusk-network/rusk/issues/2708
[#2702]: https://github.com/dusk-network/rusk/issues/2702
[#2682]: https://github.com/dusk-network/rusk/issues/2682
Expand All @@ -40,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#2402]: https://github.com/dusk-network/rusk/issues/2402
[#2401]: https://github.com/dusk-network/rusk/issues/2401
[#2400]: https://github.com/dusk-network/rusk/issues/2400
[#2396]: https://github.com/dusk-network/rusk/issues/2396
[#2340]: https://github.com/dusk-network/rusk/issues/2340
[#2288]: https://github.com/dusk-network/rusk/issues/2288

Expand Down
2 changes: 2 additions & 0 deletions rusk-wallet/src/bin/interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ pub(crate) async fn run_loop(
}
}
ProfileOp::Back => break,
ProfileOp::Stay => continue,
}
}
}
Expand Down Expand Up @@ -221,6 +222,7 @@ fn menu_profile(wallet: &Wallet<WalletFile>) -> anyhow::Result<ProfileSelect> {
enum ProfileOp {
Run(Box<Command>),
Back,
Stay,
}

/// Allows the user to load a wallet interactively
Expand Down
238 changes: 182 additions & 56 deletions rusk-wallet/src/bin/interactive/command_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
use execution_core::transfer::data::MAX_MEMO_SIZE;
use requestty::Question;
use rusk_wallet::{
currency::Dusk, gas, Address, Wallet, MAX_FUNCTION_NAME_SIZE,
currency::Dusk,
gas::{
self, DEFAULT_LIMIT_CALL, DEFAULT_LIMIT_DEPLOYMENT,
DEFAULT_LIMIT_STAKE, DEFAULT_LIMIT_TRANSFER,
},
Address, Wallet, MAX_FUNCTION_NAME_SIZE,
};

use crate::{prompt, settings::Settings, Command, Menu, WalletFile};
Expand Down Expand Up @@ -37,7 +42,7 @@ enum MenuItem {
pub(crate) fn online(
profile_idx: u8,
wallet: &Wallet<WalletFile>,
phoenix_balance: Dusk,
phoenix_spendable: Dusk,
moonlight_balance: Dusk,
settings: &Settings,
) -> anyhow::Result<ProfileOp> {
Expand Down Expand Up @@ -70,15 +75,26 @@ pub(crate) fn online(
let res = match cmd {
MenuItem::Transfer => {
let rcvr = prompt::request_rcvr_addr("recipient")?;

let (sender, balance) = match &rcvr {
Address::Shielded(_) => {
(wallet.shielded_address(profile_idx)?, phoenix_balance)
(wallet.shielded_address(profile_idx)?, phoenix_spendable)
}
Address::Public(_) => {
(wallet.public_address(profile_idx)?, moonlight_balance)
}
};

if check_min_gas_balance(
balance,
DEFAULT_LIMIT_TRANSFER,
"a transfer transaction",
)
.is_err()
{
return Ok(ProfileOp::Stay);
}

let memo = Some(prompt::request_str("memo", MAX_MEMO_SIZE)?);
let amt = if memo.is_some() {
prompt::request_optional_token_amt("transfer", balance)
Expand All @@ -98,14 +114,23 @@ pub(crate) fn online(
}))
}
MenuItem::Stake => {
let (addr, balance) = match prompt::request_transaction_model()? {
prompt::TransactionModel::Shielded => {
(wallet.shielded_address(profile_idx)?, phoenix_balance)
}
prompt::TransactionModel::Public => {
(wallet.public_address(profile_idx)?, moonlight_balance)
}
};
let (addr, balance) = pick_transaction_model(
wallet,
profile_idx,
phoenix_spendable,
moonlight_balance,
)?;

if check_min_gas_balance(
balance,
DEFAULT_LIMIT_STAKE,
"a stake transaction",
)
.is_err()
{
return Ok(ProfileOp::Stay);
}

ProfileOp::Run(Box::new(Command::Stake {
address: Some(addr),
amt: prompt::request_stake_token_amt(balance)?,
Expand All @@ -114,45 +139,71 @@ pub(crate) fn online(
}))
}
MenuItem::Unstake => {
let addr = match prompt::request_transaction_model()? {
prompt::TransactionModel::Shielded => {
wallet.shielded_address(profile_idx)
}
prompt::TransactionModel::Public => {
wallet.public_address(profile_idx)
}
}?;
let (addr, balance) = pick_transaction_model(
wallet,
profile_idx,
phoenix_spendable,
moonlight_balance,
)?;

if check_min_gas_balance(
balance,
DEFAULT_LIMIT_STAKE,
"an unstake transaction",
)
.is_err()
{
return Ok(ProfileOp::Stay);
}

ProfileOp::Run(Box::new(Command::Unstake {
address: Some(addr),
gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT_CALL)?,
gas_price: prompt::request_gas_price()?,
}))
}

MenuItem::Withdraw => {
let addr = match prompt::request_transaction_model()? {
prompt::TransactionModel::Shielded => {
wallet.shielded_address(profile_idx)
}
prompt::TransactionModel::Public => {
wallet.public_address(profile_idx)
}
}?;
let (addr, balance) = pick_transaction_model(
wallet,
profile_idx,
phoenix_spendable,
moonlight_balance,
)?;

if check_min_gas_balance(
balance,
DEFAULT_LIMIT_STAKE,
"a stake reward withdrawal transaction",
)
.is_err()
{
return Ok(ProfileOp::Stay);
}

ProfileOp::Run(Box::new(Command::Withdraw {
address: Some(addr),
gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT_CALL)?,
gas_price: prompt::request_gas_price()?,
}))
}
MenuItem::ContractDeploy => {
let addr = match prompt::request_transaction_model()? {
prompt::TransactionModel::Shielded => {
wallet.shielded_address(profile_idx)
}
prompt::TransactionModel::Public => {
wallet.public_address(profile_idx)
}
}?;
let (addr, balance) = pick_transaction_model(
wallet,
profile_idx,
phoenix_spendable,
moonlight_balance,
)?;

if check_min_gas_balance(
balance,
DEFAULT_LIMIT_DEPLOYMENT,
"a deploy transaction",
)
.is_err()
{
return Ok(ProfileOp::Stay);
}

ProfileOp::Run(Box::new(Command::ContractDeploy {
address: Some(addr),
code: prompt::request_contract_code()?,
Expand All @@ -165,14 +216,23 @@ pub(crate) fn online(
}))
}
MenuItem::ContractCall => {
let addr = match prompt::request_transaction_model()? {
prompt::TransactionModel::Shielded => {
wallet.shielded_address(profile_idx)
}
prompt::TransactionModel::Public => {
wallet.public_address(profile_idx)
}
}?;
let (addr, balance) = pick_transaction_model(
wallet,
profile_idx,
phoenix_spendable,
moonlight_balance,
)?;

if check_min_gas_balance(
balance,
DEFAULT_LIMIT_CALL,
"a contract call",
)
.is_err()
{
return Ok(ProfileOp::Stay);
}

ProfileOp::Run(Box::new(Command::ContractCall {
address: Some(addr),
contract_id: prompt::request_bytes("contract id")?,
Expand All @@ -195,18 +255,42 @@ pub(crate) fn online(
profile_idx: Some(profile_idx),
reward: false,
})),
MenuItem::Shield => ProfileOp::Run(Box::new(Command::Shield {
profile_idx: Some(profile_idx),
amt: prompt::request_token_amt("convert", moonlight_balance)?,
gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT_CALL)?,
gas_price: prompt::request_gas_price()?,
})),
MenuItem::Unshield => ProfileOp::Run(Box::new(Command::Unshield {
profile_idx: Some(profile_idx),
amt: prompt::request_token_amt("convert", phoenix_balance)?,
gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT_CALL)?,
gas_price: prompt::request_gas_price()?,
})),
MenuItem::Shield => {
if check_min_gas_balance(
moonlight_balance,
DEFAULT_LIMIT_CALL,
"convert DUSK from public to shielded",
)
.is_err()
{
return Ok(ProfileOp::Stay);
}

ProfileOp::Run(Box::new(Command::Shield {
profile_idx: Some(profile_idx),
amt: prompt::request_token_amt("convert", moonlight_balance)?,
gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT_CALL)?,
gas_price: prompt::request_gas_price()?,
}))
}
MenuItem::Unshield => {
if check_min_gas_balance(
phoenix_spendable,
DEFAULT_LIMIT_CALL,
"convert DUSK from shielded to public",
)
.is_err()
{
return Ok(ProfileOp::Stay);
}

ProfileOp::Run(Box::new(Command::Unshield {
profile_idx: Some(profile_idx),
amt: prompt::request_token_amt("convert", phoenix_spendable)?,
gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT_CALL)?,
gas_price: prompt::request_gas_price()?,
}))
}
MenuItem::CalculateContractId => {
ProfileOp::Run(Box::new(Command::CalculateContractId {
profile_idx: Some(profile_idx),
Expand Down Expand Up @@ -261,3 +345,45 @@ pub(crate) fn offline(
};
Ok(res)
}

/// Prompts the user to select a transaction model (Shielded or Public), and
/// retrieves the corresponding address and balance for the specific profile
fn pick_transaction_model(
wallet: &Wallet<WalletFile>,
profile_idx: u8,
phoenix_spendable: Dusk,
moonlight_balance: Dusk,
) -> anyhow::Result<(Address, Dusk)> {
match prompt::request_transaction_model()? {
prompt::TransactionModel::Shielded => {
let addr = wallet.shielded_address(profile_idx)?;
Ok((addr, phoenix_spendable))
}
prompt::TransactionModel::Public => {
let addr = wallet.public_address(profile_idx)?;
Ok((addr, moonlight_balance))
}
}
}

/// Verifies that the user's balance meets the minimum required gas for a given
/// action
fn check_min_gas_balance(
balance: Dusk,
min_required_gas: u64,
action: &str,
) -> anyhow::Result<()> {
let min_required_gas: Dusk = min_required_gas.into();
if balance < min_required_gas {
println!(
"Balance too low to cover the minimum gas cost for {}.",
action
);
Err(anyhow::anyhow!(
"Balance too low to cover the minimum gas cost for {}.",
action
))
} else {
Ok(())
}
}
11 changes: 7 additions & 4 deletions rusk-wallet/src/gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@
use crate::currency::Lux;

/// The minimum gas limit
pub const MIN_LIMIT: u64 = 50_000_000;
pub const MIN_LIMIT: u64 = 500_000;

/// The default gas limit for transfer transactions
pub const DEFAULT_LIMIT_TRANSFER: u64 = 100_000_000;
pub const DEFAULT_LIMIT_TRANSFER: u64 = 2_000_000;

/// The default gas limit for a contract deployment
pub const DEFAULT_LIMIT_DEPLOYMENT: u64 = 50_000_000;

/// The default gas limit for staking/contract calls
pub const DEFAULT_LIMIT_CALL: u64 = 2_900_000_000;
/// The default gas limit for contract calls
pub const DEFAULT_LIMIT_CALL: u64 = 2_000_000_000;

/// The default gas limit for stake/unstake/withdraw calls
pub const DEFAULT_LIMIT_STAKE: u64 = 50_000_000;

/// The default gas price
pub const DEFAULT_PRICE: Lux = 1;
Expand Down

0 comments on commit 3c09734

Please sign in to comment.