diff --git a/contracts/stake/tests/partial_stake.rs b/contracts/stake/tests/partial_stake.rs
index 537d4b03d..6dcff4f22 100644
--- a/contracts/stake/tests/partial_stake.rs
+++ b/contracts/stake/tests/partial_stake.rs
@@ -52,6 +52,7 @@ fn stake() -> Result<(), PiecrustError> {
let tx = moonlight_stake(
&moonlight_sk,
&stake_sk,
+ &stake_sk,
stake_1,
GAS_LIMIT,
GAS_PRICE,
@@ -79,6 +80,7 @@ fn stake() -> Result<(), PiecrustError> {
let tx = moonlight_stake(
&moonlight_sk,
&stake_sk,
+ &stake_sk,
stake_2,
GAS_LIMIT,
GAS_PRICE,
@@ -112,6 +114,7 @@ fn stake() -> Result<(), PiecrustError> {
let tx = moonlight_stake(
&moonlight_sk,
&stake_sk,
+ &stake_sk,
stake_3,
GAS_LIMIT,
GAS_PRICE,
@@ -146,6 +149,7 @@ fn stake() -> Result<(), PiecrustError> {
let tx = moonlight_stake(
&moonlight_sk,
&stake_sk,
+ &stake_sk,
stake_4,
GAS_LIMIT,
GAS_PRICE,
@@ -179,6 +183,7 @@ fn unstake() -> Result<(), PiecrustError> {
let tx = moonlight_stake(
&moonlight_sk,
&stake_sk,
+ &stake_sk,
STAKE_VALUE,
GAS_LIMIT,
GAS_PRICE,
@@ -200,6 +205,7 @@ fn unstake() -> Result<(), PiecrustError> {
rng,
&moonlight_sk,
&stake_sk,
+ &stake_sk,
unstake_1,
GAS_LIMIT,
GAS_PRICE,
@@ -229,6 +235,7 @@ fn unstake() -> Result<(), PiecrustError> {
let tx = moonlight_stake(
&moonlight_sk,
&stake_sk,
+ &stake_sk,
unstake_1,
GAS_LIMIT,
GAS_PRICE,
@@ -254,6 +261,7 @@ fn unstake() -> Result<(), PiecrustError> {
rng,
&moonlight_sk,
&stake_sk,
+ &stake_sk,
unstake_2,
GAS_LIMIT,
GAS_PRICE,
@@ -291,6 +299,7 @@ fn unstake() -> Result<(), PiecrustError> {
rng,
&moonlight_sk,
&stake_sk,
+ &stake_sk,
unstake_3,
GAS_LIMIT,
GAS_PRICE,
@@ -333,6 +342,7 @@ fn withdraw_reward() -> Result<(), PiecrustError> {
let tx = moonlight_stake(
&moonlight_sk,
&stake_sk,
+ &stake_sk,
STAKE_VALUE,
GAS_LIMIT,
GAS_PRICE,
@@ -357,6 +367,7 @@ fn withdraw_reward() -> Result<(), PiecrustError> {
rng,
&moonlight_sk,
&stake_sk,
+ &stake_sk,
reward_withdawal_1,
GAS_LIMIT,
GAS_PRICE,
@@ -391,6 +402,7 @@ fn withdraw_reward() -> Result<(), PiecrustError> {
rng,
&moonlight_sk,
&stake_sk,
+ &stake_sk,
reward_withdawal_2,
GAS_LIMIT,
GAS_PRICE,
diff --git a/contracts/stake/tests/stake.rs b/contracts/stake/tests/stake.rs
index 7719bd700..265405499 100644
--- a/contracts/stake/tests/stake.rs
+++ b/contracts/stake/tests/stake.rs
@@ -64,7 +64,7 @@ fn stake_withdraw_unstake() {
// Stake
// Fashion a Stake struct
- let stake = Stake::new(&stake_sk, INITIAL_STAKE, CHAIN_ID);
+ let stake = Stake::new(&stake_sk, &stake_sk, INITIAL_STAKE, CHAIN_ID);
let stake_bytes = rkyv::to_bytes::<_, 1024>(&stake)
.expect("Should serialize Stake correctly")
.to_vec();
@@ -153,7 +153,7 @@ fn stake_withdraw_unstake() {
input_notes[1].gen_nullifier(&phoenix_sender_sk),
]),
);
- let withdraw = StakeWithdraw::new(&stake_sk, withdraw);
+ let withdraw = StakeWithdraw::new(&stake_sk, &stake_sk, withdraw);
let withdraw_bytes = rkyv::to_bytes::<_, 2048>(&withdraw)
.expect("Serializing Withdraw should succeed")
@@ -248,7 +248,7 @@ fn stake_withdraw_unstake() {
]),
);
- let unstake = StakeWithdraw::new(&stake_sk, withdraw);
+ let unstake = StakeWithdraw::new(&stake_sk, &stake_sk, withdraw);
let unstake_bytes = rkyv::to_bytes::<_, 2048>(&unstake)
.expect("Serializing Unstake should succeed")
diff --git a/execution-core/src/stake.rs b/execution-core/src/stake.rs
index a99960602..de74d9227 100644
--- a/execution-core/src/stake.rs
+++ b/execution-core/src/stake.rs
@@ -51,12 +51,18 @@ impl Stake {
const MESSAGE_SIZE: usize =
1 + BlsPublicKey::SIZE + BlsPublicKey::SIZE + u64::SIZE;
- /// Create a new stake.
+ /// Create a new stake specifying the owner.
#[must_use]
- pub fn new(sk: &BlsSecretKey, value: u64, chain_id: u8) -> Self {
- let key = BlsPublicKey::from(sk);
+ pub fn new(
+ account_sk: &BlsSecretKey,
+ owner_sk: &BlsSecretKey,
+ value: u64,
+ chain_id: u8,
+ ) -> Self {
+ let account = BlsPublicKey::from(account_sk);
+ let owner = BlsPublicKey::from(owner_sk);
- let keys = StakeKeys::single_key(key);
+ let keys = StakeKeys::new(account, owner);
let mut stake = Stake {
chain_id,
@@ -68,14 +74,14 @@ impl Stake {
let msg = stake.signature_message();
stake.signature = DoubleSignature {
- account: sk.sign(&msg),
- owner: sk.sign(&msg),
+ account: account_sk.sign(&msg),
+ owner: owner_sk.sign(&msg),
};
stake
}
- /// Create a new stake.
+ /// Create a new stake from a contract.
#[must_use]
pub fn new_from_contract(
sk: &BlsSecretKey,
@@ -249,10 +255,15 @@ pub struct Withdraw {
}
impl Withdraw {
- /// Create a new withdraw call.
+ /// Create a new withdraw call specifying the owner.
#[must_use]
- pub fn new(sk: &BlsSecretKey, withdraw: TransferWithdraw) -> Self {
- let account = BlsPublicKey::from(sk);
+ pub fn new(
+ account_sk: &BlsSecretKey,
+ owner_sk: &BlsSecretKey,
+ withdraw: TransferWithdraw,
+ ) -> Self {
+ let account = BlsPublicKey::from(account_sk);
+
let mut stake_withdraw = Withdraw {
account,
withdraw,
@@ -262,13 +273,22 @@ impl Withdraw {
let msg = stake_withdraw.signature_message();
stake_withdraw.signature = DoubleSignature {
- account: sk.sign(&msg),
- owner: sk.sign(&msg),
+ account: account_sk.sign(&msg),
+ owner: owner_sk.sign(&msg),
};
stake_withdraw
}
+ /// Create a new withdraw call using the same account as the owner.
+ #[must_use]
+ pub fn with_single_key(
+ sk: &BlsSecretKey,
+ withdraw: TransferWithdraw,
+ ) -> Self {
+ Self::new(sk, sk, withdraw)
+ }
+
/// The public key to withdraw from.
#[must_use]
pub fn account(&self) -> &BlsPublicKey {
diff --git a/rusk-wallet/src/bin/command.rs b/rusk-wallet/src/bin/command.rs
index 3e0233326..75fbce712 100644
--- a/rusk-wallet/src/bin/command.rs
+++ b/rusk-wallet/src/bin/command.rs
@@ -159,6 +159,10 @@ pub(crate) enum Command {
#[arg(long)]
address: Option
,
+ /// Owner of the stake [default: same Public address of the stake]
+ #[arg(long)]
+ owner: Option,
+
/// Amount of DUSK to stake
#[arg(short, long)]
amt: Dusk,
@@ -393,21 +397,28 @@ impl Command {
}
Command::Stake {
address,
+ owner,
amt,
gas_limit,
gas_price,
} => {
let address = address.unwrap_or(wallet.default_address());
let addr_idx = wallet.find_index(&address)?;
+ let owner_idx =
+ owner.map(|owner| wallet.find_index(&owner)).transpose()?;
let gas = Gas::new(gas_limit).with_price(gas_price);
let tx = match address {
Address::Shielded(_) => {
wallet.sync().await?;
- wallet.phoenix_stake(addr_idx, amt, gas).await
+ wallet
+ .phoenix_stake(addr_idx, owner_idx, amt, gas)
+ .await
}
Address::Public(_) => {
- wallet.moonlight_stake(addr_idx, amt, gas).await
+ wallet
+ .moonlight_stake(addr_idx, owner_idx, amt, gas)
+ .await
}
}?;
diff --git a/rusk-wallet/src/bin/interactive.rs b/rusk-wallet/src/bin/interactive.rs
index 129594d64..d3330d5c1 100644
--- a/rusk-wallet/src/bin/interactive.rs
+++ b/rusk-wallet/src/bin/interactive.rs
@@ -373,6 +373,7 @@ fn confirm(cmd: &Command, wallet: &Wallet) -> anyhow::Result {
}
Command::Stake {
address,
+ owner,
amt,
gas_limit,
gas_price,
@@ -380,8 +381,10 @@ fn confirm(cmd: &Command, wallet: &Wallet) -> anyhow::Result {
let sender = address.as_ref().ok_or(Error::BadAddress)?;
let max_fee = gas_limit * gas_price;
let stake_to = wallet.public_address(wallet.find_index(sender)?)?;
+ let owner = owner.as_ref().unwrap_or(&stake_to);
println!(" > Pay with {}", sender.preview());
println!(" > Stake to {}", stake_to.preview());
+ println!(" > Stake owner {}", owner.preview());
println!(" > Amount to stake = {} DUSK", amt);
println!(" > Max fee = {} DUSK", Dusk::from(max_fee));
if let Address::Public(_) = sender {
diff --git a/rusk-wallet/src/bin/interactive/command_menu.rs b/rusk-wallet/src/bin/interactive/command_menu.rs
index f995787c7..3003027e4 100644
--- a/rusk-wallet/src/bin/interactive/command_menu.rs
+++ b/rusk-wallet/src/bin/interactive/command_menu.rs
@@ -14,7 +14,7 @@ use rusk_wallet::gas::{
DEFAULT_PRICE, GAS_PER_DEPLOY_BYTE, MIN_PRICE_DEPLOYMENT,
};
use rusk_wallet::{
- Address, Wallet, MAX_CONTRACT_INIT_ARG_SIZE, MAX_FUNCTION_NAME_SIZE,
+ Address, Error, Wallet, MAX_CONTRACT_INIT_ARG_SIZE, MAX_FUNCTION_NAME_SIZE,
};
use super::ProfileOp;
@@ -162,8 +162,29 @@ pub(crate) async fn online(
let mempool_gas_prices = wallet.get_mempool_gas_prices().await?;
+ let stake_idx = wallet
+ .find_index(&addr)
+ .expect("index to exists in interactive mode");
+ let stake_pk = wallet
+ .public_key(stake_idx)
+ .expect("public key to exists in interactive mode");
+
+ let owner = match wallet.find_stake_owner_account(stake_pk).await {
+ Ok(account) => account,
+ Err(Error::NotStaked) => {
+ let choices = wallet
+ .profiles()
+ .iter()
+ .map(|p| Address::Public(p.public_addr))
+ .collect();
+ prompt::request_address(stake_idx, choices)?
+ }
+ e => e?,
+ };
+
ProfileOp::Run(Box::new(Command::Stake {
address: Some(addr),
+ owner: Some(owner),
amt: prompt::request_stake_token_amt(balance)?,
gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT_CALL)?,
gas_price: prompt::request_gas_price(
diff --git a/rusk-wallet/src/bin/io/prompt.rs b/rusk-wallet/src/bin/io/prompt.rs
index ecae173c9..7498d2c19 100644
--- a/rusk-wallet/src/bin/io/prompt.rs
+++ b/rusk-wallet/src/bin/io/prompt.rs
@@ -354,6 +354,19 @@ pub(crate) fn request_transaction_model() -> anyhow::Result {
)
}
+/// Request transaction model to use
+pub(crate) fn request_address(
+ current_idx: u8,
+ choices: Vec,
+) -> anyhow::Result {
+ Ok(Select::new(
+ "Please select the moonlight address to use as stake owner",
+ choices,
+ )
+ .with_starting_cursor(current_idx as usize)
+ .prompt()?)
+}
+
/// Request contract WASM file location
pub(crate) fn request_contract_code() -> anyhow::Result {
let validator = |path_str: &str| {
diff --git a/rusk-wallet/src/clients.rs b/rusk-wallet/src/clients.rs
index 2836efa92..368074d86 100644
--- a/rusk-wallet/src/clients.rs
+++ b/rusk-wallet/src/clients.rs
@@ -11,6 +11,7 @@ use std::sync::{Arc, Mutex};
use dusk_bytes::Serializable;
use execution_core::signatures::bls::PublicKey as BlsPublicKey;
+use execution_core::stake::{StakeFundOwner, StakeKeys};
use execution_core::transfer::moonlight::AccountData;
use execution_core::transfer::phoenix::{Note, NoteLeaf, Prove};
use execution_core::transfer::Transaction;
@@ -322,6 +323,33 @@ impl State {
Ok(stake_data)
}
+ /// Get the stake owner of a given stake account.
+ pub(crate) async fn fetch_stake_owner(
+ &self,
+ pk: &BlsPublicKey,
+ ) -> Result