Skip to content

Commit f4b3b79

Browse files
committed
PoS: allow to set a receiver target for claim reward tx
1 parent 060bf3c commit f4b3b79

File tree

11 files changed

+162
-58
lines changed

11 files changed

+162
-58
lines changed

crates/apps_lib/src/cli.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3627,6 +3627,7 @@ pub mod args {
36273627
pub const RAW_PUBLIC_KEY_HASH_OPT: ArgOpt<String> =
36283628
RAW_PUBLIC_KEY_HASH.opt();
36293629
pub const RECEIVER: Arg<String> = arg("receiver");
3630+
pub const RECEIVER_ADDR: ArgOpt<WalletAddress> = arg_opt("receiver");
36303631
pub const REFUND_TARGET: ArgOpt<WalletTransferTarget> =
36313632
arg_opt("refund-target");
36323633
pub const RELAYER: Arg<Address> = arg("relayer");
@@ -6359,6 +6360,7 @@ pub mod args {
63596360
tx,
63606361
validator: chain_ctx.get(&self.validator),
63616362
source: self.source.map(|x| chain_ctx.get(&x)),
6363+
receiver: self.receiver.map(|x| chain_ctx.get(&x)),
63626364
tx_code_path: self.tx_code_path.to_path_buf(),
63636365
})
63646366
}
@@ -6369,11 +6371,13 @@ pub mod args {
63696371
let tx = Tx::parse(matches);
63706372
let validator = VALIDATOR.parse(matches);
63716373
let source = SOURCE_OPT.parse(matches);
6374+
let receiver = RECEIVER_ADDR.parse(matches);
63726375
let tx_code_path = PathBuf::from(TX_CLAIM_REWARDS_WASM);
63736376
Self {
63746377
tx,
63756378
validator,
63766379
source,
6380+
receiver,
63776381
tx_code_path,
63786382
}
63796383
}
@@ -6385,6 +6389,11 @@ pub mod args {
63856389
"Source address for claiming rewards for a bond. For \
63866390
self-bonds, the validator is also the source."
63876391
)))
6392+
.arg(RECEIVER_ADDR.def().help(wrap!(
6393+
"An optional receiver address. If not given, the rewards \
6394+
will be received by the owner (i.e. the source of a \
6395+
delegation or the validator of a self-bond)."
6396+
)))
63886397
}
63896398
}
63906399

crates/node/src/shell/finalize_block.rs

Lines changed: 59 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2444,15 +2444,18 @@ mod test_finalize_block {
24442444
.unwrap();
24452445

24462446
// Claim the rewards from the initial epoch
2447-
let reward_1 =
2448-
proof_of_stake::claim_reward_tokens::<
2449-
_,
2450-
governance::Store<_>,
2451-
token::Store<_>,
2452-
>(
2453-
&mut shell.state, None, &validator.address, current_epoch
2454-
)
2455-
.unwrap();
2447+
let reward_1 = proof_of_stake::claim_reward_tokens::<
2448+
_,
2449+
governance::Store<_>,
2450+
token::Store<_>,
2451+
>(
2452+
&mut shell.state,
2453+
None,
2454+
&validator.address,
2455+
None,
2456+
current_epoch,
2457+
)
2458+
.unwrap();
24562459
total_claimed += reward_1;
24572460
assert_eq!(reward_1, query_rewards);
24582461
assert!(is_reward_equal_enough(total_rewards, total_claimed, 1));
@@ -2481,7 +2484,11 @@ mod test_finalize_block {
24812484
governance::Store<_>,
24822485
token::Store<_>,
24832486
>(
2484-
&mut shell.state, None, &validator.address, current_epoch
2487+
&mut shell.state,
2488+
None,
2489+
&validator.address,
2490+
None,
2491+
current_epoch,
24852492
)
24862493
.unwrap();
24872494
assert_eq!(att, token::Amount::zero());
@@ -2520,7 +2527,11 @@ mod test_finalize_block {
25202527
governance::Store<_>,
25212528
token::Store<_>,
25222529
>(
2523-
&mut shell.state, None, &validator.address, current_epoch
2530+
&mut shell.state,
2531+
None,
2532+
&validator.address,
2533+
None,
2534+
current_epoch,
25242535
)
25252536
.unwrap();
25262537
total_claimed += rew;
@@ -2601,15 +2612,18 @@ mod test_finalize_block {
26012612
.unwrap();
26022613

26032614
// Claim tokens
2604-
let reward_2 =
2605-
proof_of_stake::claim_reward_tokens::<
2606-
_,
2607-
governance::Store<_>,
2608-
token::Store<_>,
2609-
>(
2610-
&mut shell.state, None, &validator.address, current_epoch
2611-
)
2612-
.unwrap();
2615+
let reward_2 = proof_of_stake::claim_reward_tokens::<
2616+
_,
2617+
governance::Store<_>,
2618+
token::Store<_>,
2619+
>(
2620+
&mut shell.state,
2621+
None,
2622+
&validator.address,
2623+
None,
2624+
current_epoch,
2625+
)
2626+
.unwrap();
26132627
total_claimed += reward_2;
26142628
assert_eq!(query_rewards, reward_2);
26152629

@@ -2730,15 +2744,18 @@ mod test_finalize_block {
27302744
}
27312745

27322746
// Claim the rewards for the validator for the first two epochs
2733-
let val_reward_1 =
2734-
proof_of_stake::claim_reward_tokens::<
2735-
_,
2736-
governance::Store<_>,
2737-
token::Store<_>,
2738-
>(
2739-
&mut shell.state, None, &validator.address, current_epoch
2740-
)
2741-
.unwrap();
2747+
let val_reward_1 = proof_of_stake::claim_reward_tokens::<
2748+
_,
2749+
governance::Store<_>,
2750+
token::Store<_>,
2751+
>(
2752+
&mut shell.state,
2753+
None,
2754+
&validator.address,
2755+
None,
2756+
current_epoch,
2757+
)
2758+
.unwrap();
27422759
total_claimed += val_reward_1;
27432760
assert!(is_reward_equal_enough(
27442761
total_rewards,
@@ -2758,15 +2775,18 @@ mod test_finalize_block {
27582775
total_rewards += inflation_3;
27592776

27602777
// Claim again for the validator
2761-
let val_reward_2 =
2762-
proof_of_stake::claim_reward_tokens::<
2763-
_,
2764-
governance::Store<_>,
2765-
token::Store<_>,
2766-
>(
2767-
&mut shell.state, None, &validator.address, current_epoch
2768-
)
2769-
.unwrap();
2778+
let val_reward_2 = proof_of_stake::claim_reward_tokens::<
2779+
_,
2780+
governance::Store<_>,
2781+
token::Store<_>,
2782+
>(
2783+
&mut shell.state,
2784+
None,
2785+
&validator.address,
2786+
None,
2787+
current_epoch,
2788+
)
2789+
.unwrap();
27702790

27712791
// Claim for the delegator
27722792
let del_reward_1 = proof_of_stake::claim_reward_tokens::<
@@ -2777,6 +2797,7 @@ mod test_finalize_block {
27772797
&mut shell.state,
27782798
Some(&delegator),
27792799
&validator.address,
2800+
None,
27802801
current_epoch,
27812802
)
27822803
.unwrap();

crates/proof_of_stake/src/lib.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2805,6 +2805,7 @@ pub fn claim_reward_tokens<S, Gov, Token>(
28052805
storage: &mut S,
28062806
source: Option<&Address>,
28072807
validator: &Address,
2808+
receiver: Option<&Address>,
28082809
current_epoch: Epoch,
28092810
) -> Result<token::Amount>
28102811
where
@@ -2814,8 +2815,14 @@ where
28142815
{
28152816
tracing::debug!("Claiming rewards in epoch {current_epoch}");
28162817

2817-
let source = source.cloned().unwrap_or_else(|| validator.clone());
2818-
tracing::debug!("Source {} --> Validator {}", source, validator);
2818+
let source = source.unwrap_or(validator).clone();
2819+
let receiver = receiver.unwrap_or(&source).clone();
2820+
tracing::debug!(
2821+
"Source {} --> Validator {}, Receiver {}",
2822+
source,
2823+
validator,
2824+
receiver
2825+
);
28192826

28202827
let mut reward_tokens = compute_current_rewards_from_bonds::<S, Gov>(
28212828
storage,
@@ -2832,17 +2839,23 @@ where
28322839
// Update the last claim epoch in storage
28332840
write_last_reward_claim_epoch(storage, &source, validator, current_epoch)?;
28342841

2835-
// Transfer the bonded tokens from PoS to the source
2842+
// Transfer the bonded tokens from PoS to the receiver
28362843
let staking_token = staking_token_address(storage);
2837-
Token::transfer(storage, &staking_token, &ADDRESS, &source, reward_tokens)?;
2844+
Token::transfer(
2845+
storage,
2846+
&staking_token,
2847+
&ADDRESS,
2848+
&receiver,
2849+
reward_tokens,
2850+
)?;
28382851
Token::emit_transfer_event(
28392852
storage,
28402853
CLAIM_REWARDS_EVENT_DESC.into(),
28412854
trans_token::EventLevel::Tx,
28422855
&staking_token,
28432856
reward_tokens,
28442857
trans_token::UserAccount::Internal(ADDRESS),
2845-
trans_token::UserAccount::Internal(source),
2858+
trans_token::UserAccount::Internal(receiver),
28462859
)?;
28472860

28482861
Ok(reward_tokens)

crates/sdk/src/args.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1880,6 +1880,10 @@ pub struct ClaimRewards<C: NamadaTypes = SdkTypes> {
18801880
/// Source address for claiming rewards due to bonds. For self-bonds, the
18811881
/// validator is also the source
18821882
pub source: Option<C::Address>,
1883+
/// An optional receiver address. If not given, the rewards will be
1884+
/// received by the owner (i.e. the source of a delegation or the
1885+
/// validator of a self-bond).
1886+
pub receiver: Option<C::Address>,
18831887
/// Path to the TX WASM code file
18841888
pub tx_code_path: PathBuf,
18851889
}

crates/sdk/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ pub trait Namada: NamadaIo {
535535
args::ClaimRewards {
536536
validator,
537537
source: None,
538+
receiver: None,
538539
tx_code_path: PathBuf::from(TX_CLAIM_REWARDS_WASM),
539540
tx: self.tx_builder(),
540541
}

crates/sdk/src/queries/vp/pos.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,14 +1248,15 @@ mod test {
12481248
.expect("Test failed");
12491249
client.state.in_mem_mut().block.height = height + 1;
12501250

1251-
let claimed = namada_proof_of_stake::claim_reward_tokens::<
1252-
_,
1253-
governance::Store<_>,
1254-
namada_token::Store<_>,
1255-
>(
1256-
&mut client.state, Some(&delegator), &validator, epoch
1257-
)
1258-
.expect("Claiming rewards failed");
1251+
let claimed =
1252+
namada_proof_of_stake::claim_reward_tokens::<
1253+
_,
1254+
governance::Store<_>,
1255+
namada_token::Store<_>,
1256+
>(
1257+
&mut client.state, Some(&delegator), &validator, None, epoch
1258+
)
1259+
.expect("Claiming rewards failed");
12591260

12601261
assert_eq!(claimed, del_reward_epoch_3 + del_reward_epoch_2);
12611262

@@ -1264,7 +1265,7 @@ mod test {
12641265
_,
12651266
governance::Store<_>,
12661267
namada_token::Store<_>,
1267-
>(&mut client.state, None, &validator, epoch)
1268+
>(&mut client.state, None, &validator, None, epoch)
12681269
.expect("Claiming validator rewards failed");
12691270

12701271
assert_eq!(claimed_validator, val_reward_epoch_3 + val_reward_epoch_2);

crates/sdk/src/tx.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1726,6 +1726,7 @@ pub async fn build_claim_rewards(
17261726
tx: tx_args,
17271727
validator,
17281728
source,
1729+
receiver,
17291730
tx_code_path,
17301731
}: &args::ClaimRewards,
17311732
) -> Result<(Tx, SigningTxData)> {
@@ -1757,7 +1758,30 @@ pub async fn build_claim_rewards(
17571758
None => Ok(source.clone()),
17581759
}?;
17591760

1760-
let data = pos::ClaimRewards { validator, source };
1761+
// Check that the receiver address exists on chain
1762+
let receiver = match receiver.clone() {
1763+
Some(receiver) => {
1764+
let message = format!(
1765+
"The receiver address {receiver} doesn't exist on chain."
1766+
);
1767+
address_exists_or_err(
1768+
receiver,
1769+
tx_args.force,
1770+
context,
1771+
message,
1772+
|err| Error::from(TxSubmitError::LocationDoesNotExist(err)),
1773+
)
1774+
.await
1775+
.map(Some)
1776+
}
1777+
None => Ok(receiver.clone()),
1778+
}?;
1779+
1780+
let data = pos::ClaimRewards {
1781+
validator,
1782+
source,
1783+
receiver,
1784+
};
17611785

17621786
build(
17631787
context,

crates/tx/src/action.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use namada_core::storage::KeySeg;
1515
use namada_core::{address, storage};
1616

1717
pub use crate::data::pos::{
18-
Bond, ClaimRewards, Redelegation, Unbond, Withdraw,
18+
Bond, ClaimRewardsCompat as ClaimRewards, Redelegation, Unbond, Withdraw,
1919
};
2020

2121
/// Actions applied from txs.

crates/tx/src/data/pos.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,23 @@ pub struct ClaimRewards {
128128
/// Source address for claiming rewards from a bond. For self-bonds, the
129129
/// validator is also the source
130130
pub source: Option<Address>,
131+
/// Optional rewards receiver address
132+
pub receiver: Option<Address>,
133+
}
134+
135+
/// Compatibility data definition from previous version (before addition of a
136+
/// receiver field).
137+
///
138+
/// TODO: Temporarily replaces `crate::data::pos::ClaimRewards` that now
139+
/// contains an additional `receiver` field to maintain consensus compatibility.
140+
/// PoS VP reads this action and will need to be updated to use the new field.
141+
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)]
142+
pub struct ClaimRewardsCompat {
143+
/// Validator address
144+
pub validator: Address,
145+
/// Source address for claiming rewards from a bond. For self-bonds, the
146+
/// validator is also the source
147+
pub source: Option<Address>,
131148
}
132149

133150
/// A redelegation of bonded tokens from one validator to another.

crates/tx_prelude/src/proof_of_stake.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ impl Ctx {
203203
&mut self,
204204
source: Option<&Address>,
205205
validator: &Address,
206+
receiver: Option<&Address>,
206207
) -> Result<token::Amount> {
207208
// The tx must be authorized by the source address
208209
let verifier = source.as_ref().unwrap_or(&validator);
@@ -218,6 +219,7 @@ impl Ctx {
218219
self,
219220
source,
220221
validator,
222+
receiver,
221223
current_epoch,
222224
)
223225
}

0 commit comments

Comments
 (0)