diff --git a/contracts/stake/src/lib.rs b/contracts/stake/src/lib.rs index 1237da3170..3bc2d29d35 100644 --- a/contracts/stake/src/lib.rs +++ b/contracts/stake/src/lib.rs @@ -92,9 +92,9 @@ unsafe fn insert_stake(arg_len: u32) -> u32 { #[no_mangle] unsafe fn reward(arg_len: u32) -> u32 { - rusk_abi::wrap_call(arg_len, |(pk, value)| { + rusk_abi::wrap_call(arg_len, |arg| { assert_external_caller(); - STATE.reward(&pk, value); + STATE.reward(arg); }) } diff --git a/contracts/stake/src/state.rs b/contracts/stake/src/state.rs index d46afddf9e..84b3a5e4b2 100644 --- a/contracts/stake/src/state.rs +++ b/contracts/stake/src/state.rs @@ -5,6 +5,8 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use alloc::collections::BTreeMap; +use alloc::vec::Vec; + use core::cmp::min; use dusk_bytes::Serializable; @@ -12,7 +14,7 @@ use dusk_bytes::Serializable; use execution_core::{ signatures::bls::PublicKey as BlsPublicKey, stake::{ - next_epoch, Stake, StakeAmount, StakeData, StakeEvent, + next_epoch, Reward, Stake, StakeAmount, StakeData, StakeEvent, StakeWithReceiverEvent, Withdraw, EPOCH, MINIMUM_STAKE, STAKE_CONTRACT, STAKE_WARNINGS, }, @@ -263,19 +265,23 @@ impl StakeState { /// Rewards a `account` with the given `value`. If a stake does not exist /// in the map for the key one will be created. - pub fn reward(&mut self, account: &BlsPublicKey, value: u64) { + pub fn reward(&mut self, rewards: Vec) { + // since we assure that reward is called at least once per block, this + // call is necessary to ensure that there are no inconsistencies in + // `prev_block_state`. self.check_new_block(); - let stake = self.load_or_create_stake_mut(account); + for reward in &rewards { + let stake = self.load_or_create_stake_mut(&reward.account); - // Reset faults counters - stake.faults = 0; - stake.hard_faults = 0; + // Reset faults counters + stake.faults = 0; + stake.hard_faults = 0; - stake.reward += value; + stake.reward += reward.value; + } - let account = *account; - rusk_abi::emit("reward", StakeEvent { account, value }); + rusk_abi::emit("reward", rewards); } /// Total amount burned since the genesis diff --git a/contracts/stake/tests/common/assert.rs b/contracts/stake/tests/common/assert.rs index 9b3cf0b0ac..bbbac2580e 100644 --- a/contracts/stake/tests/common/assert.rs +++ b/contracts/stake/tests/common/assert.rs @@ -9,7 +9,7 @@ use rkyv::{check_archived_root, Deserialize, Infallible}; use execution_core::{ signatures::bls::PublicKey as BlsPublicKey, - stake::{StakeEvent, StakeWithReceiverEvent}, + stake::{Reward, StakeEvent, StakeWithReceiverEvent}, Event, }; @@ -37,6 +37,13 @@ pub fn assert_event( .expect("Infallible"); assert_eq!(staking_event_data.value, should_amount); assert_eq!(staking_event_data.account.to_bytes(), should_pk.to_bytes()); + } else if topic == "reward" { + let reward_event_data = rkyv::from_bytes::>(&event.data) + .expect("Reward event data should deserialize correctly"); + + assert!(reward_event_data.iter().any(|reward| { + &reward.account == should_pk && reward.value == should_amount + })) } else { let staking_event_data = check_archived_root::(event.data.as_slice()) diff --git a/contracts/stake/tests/events.rs b/contracts/stake/tests/events.rs index 780d0b08ff..0bc0ccaa25 100644 --- a/contracts/stake/tests/events.rs +++ b/contracts/stake/tests/events.rs @@ -12,7 +12,7 @@ use rand::SeedableRng; use execution_core::{ dusk, signatures::bls::{PublicKey as BlsPublicKey, SecretKey as BlsSecretKey}, - stake::{StakeAmount, StakeData, STAKE_CONTRACT}, + stake::{Reward, RewardReason, StakeAmount, StakeData, STAKE_CONTRACT}, transfer::{ phoenix::{ PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey, @@ -72,12 +72,14 @@ fn reward_slash() -> Result<(), PiecrustError> { u64::MAX, )?; - let receipt = session.call::<_, ()>( - STAKE_CONTRACT, - "reward", - &(stake_pk, reward_amount), - u64::MAX, - )?; + let rewards = vec![Reward { + account: stake_pk, + value: reward_amount, + reason: RewardReason::Other, + }]; + + let receipt = + session.call::<_, ()>(STAKE_CONTRACT, "reward", &rewards, u64::MAX)?; assert_event(&receipt.events, "reward", &stake_pk, reward_amount); let receipt = session.call::<_, ()>( @@ -197,12 +199,14 @@ fn stake_hard_slash() -> Result<(), PiecrustError> { assert_event(&receipt.events, "hard_slash", &stake_pk, hard_slash_amount); cur_balance -= hard_slash_amount; - let receipt = session.call::<_, ()>( - STAKE_CONTRACT, - "reward", - &(stake_pk, reward_amount), - u64::MAX, - )?; + let rewards = vec![Reward { + account: stake_pk, + value: reward_amount, + reason: RewardReason::Other, + }]; + + let receipt = + session.call::<_, ()>(STAKE_CONTRACT, "reward", &rewards, u64::MAX)?; assert_event(&receipt.events, "reward", &stake_pk, reward_amount); // Simple hard fault post-reward (slash 10%) diff --git a/contracts/stake/tests/stake.rs b/contracts/stake/tests/stake.rs index 8c8d4d72b2..9834c00e4f 100644 --- a/contracts/stake/tests/stake.rs +++ b/contracts/stake/tests/stake.rs @@ -13,7 +13,10 @@ use rand::SeedableRng; use execution_core::{ dusk, signatures::bls::{PublicKey as BlsPublicKey, SecretKey as BlsSecretKey}, - stake::{Stake, StakeData, Withdraw as StakeWithdraw, STAKE_CONTRACT}, + stake::{ + Reward, RewardReason, Stake, StakeData, Withdraw as StakeWithdraw, + STAKE_CONTRACT, + }, transfer::{ data::ContractCall, phoenix::{ @@ -132,13 +135,14 @@ fn stake_withdraw_unstake() { const REWARD_AMOUNT: u64 = dusk(5.0); + let rewards = vec![Reward { + account: stake_pk, + value: REWARD_AMOUNT, + reason: RewardReason::Other, + }]; + let receipt = session - .call::<_, ()>( - STAKE_CONTRACT, - "reward", - &(stake_pk, REWARD_AMOUNT), - POINT_LIMIT, - ) + .call::<_, ()>(STAKE_CONTRACT, "reward", &rewards, POINT_LIMIT) .expect("Rewarding a key should succeed"); assert_event(&receipt.events, "reward", &stake_pk, REWARD_AMOUNT);