@@ -9,6 +9,8 @@ use nimiq_primitives::coin::Coin;
99use nimiq_primitives:: policy:: Policy ;
1010use serde:: { Deserialize , Serialize } ;
1111
12+ use super :: AddStakeReceipt ;
13+ use crate :: BalanceType ;
1214#[ cfg( feature = "interaction-traits" ) ]
1315use crate :: {
1416 account:: staking_contract:: {
@@ -26,8 +28,10 @@ use crate::{
2628///
2729/// Actions concerning a staker are:
2830/// 1. Create: Creates a staker.
29- /// 2. AddStake: Adds coins from any outside address to the staker's active balance.
30- /// This action is only possible if:
31+ /// 2. AddStake: Adds coins from any outside address to the staker.
32+ /// The balance to accredit these funds is preferentially the active balance. If there is no
33+ /// positive active balance, it credits the inactive balance instead.
34+ /// This operation has a minimum value of minimum stake, this it always respects the invariant:
3135/// (a) the resulting non-retired funds respect the invariant 1 - minimum stake for non-retired funds.
3236/// 3. SetActiveStake: Re-balances between active and inactive stake by setting the amount of active stake.
3337/// This action restarts the lock-up period of the inactive stake.
@@ -146,6 +150,7 @@ impl Staker {
146150 /// Invariants:
147151 /// (1) active + inactive balances must be == 0 or >= minimum stake
148152 /// (2) active + inactive + retired balances must be == 0 or >= minimum stake
153+ /// IMPORTANT: This code is shared between new and legacy add stake versions.
149154 pub ( crate ) fn enforce_min_stake (
150155 active_balance : Coin ,
151156 inactive_balance : Coin ,
@@ -276,75 +281,127 @@ impl StakingContract {
276281 Ok ( ( ) )
277282 }
278283
279- /// Adds more Coins to a staker's balance. It will be directly added to the staker's balance.
284+ /// Adds more Coins to a staker's balance. It adds to the staker's active balance
285+ /// if there are some funds on it (active balance >0). Otherwise, it credits the value to
286+ /// the inactive balance.
280287 /// Anyone can add stake for a staker. The staker must already exist.
281288 pub fn add_stake (
282289 & mut self ,
283290 store : & mut StakingContractStoreWrite ,
284291 staker_address : & Address ,
285292 value : Coin ,
293+ protocol_version : u16 ,
286294 tx_logger : & mut TransactionLog ,
287- ) -> Result < ( ) , AccountError > {
295+ ) -> Result < AddStakeReceipt , AccountError > {
296+ if protocol_version < Policy :: ADD_STAKE_PROTOCOL_UPGRADE_VERSION {
297+ log:: warn!( %protocol_version, "Adding stake using old protocol version" ) ;
298+ return self . legacy_add_stake_0v ( store, staker_address, value, tx_logger) ;
299+ }
288300 // Get the staker.
289301 let mut staker = store. expect_staker ( staker_address) ?;
290302
291- // Fail if the minimum stake would be violated for the non-retired funds (invariant 1).
292- Staker :: enforce_min_stake (
293- staker. active_balance + value,
294- staker. inactive_balance ,
295- staker. retired_balance ,
296- ) ?;
297-
298- // All checks passed, not allowed to fail from here on!
299-
300- // If we are delegating to a validator, we need to update it.
303+ // Check that the delegation is still valid, i.e. the validator hasn't been deleted.
301304 if let Some ( validator_address) = & staker. delegation {
302- // Check that the delegation is still valid, i.e. the validator hasn't been deleted.
303305 store. expect_validator ( validator_address) ?;
304- self . increase_stake_to_validator ( store, validator_address, value) ;
305306 }
306307
308+ // Add stake txs never violate minimum stake for the non-retired funds (invariant 1),
309+ // because the intrinsic tx checks that value is >= min stake.
310+ assert ! (
311+ Staker :: enforce_min_stake(
312+ staker. active_balance + value,
313+ staker. inactive_balance,
314+ staker. retired_balance,
315+ )
316+ . is_ok( ) ,
317+ "Add stake should never violate the min stake invariants"
318+ ) ;
319+
320+ // All checks passed, not allowed to fail from here on!
321+
307322 // Update the staker's and staking contract's balances.
308- staker. active_balance += value;
323+ // We want to preferentially credit the active balance. Only if there is no active balance,
324+ // then it will attribute the funds to the inactive balance.
325+ let credited_balance = if !staker. active_balance . is_zero ( ) {
326+ staker. active_balance += value;
327+ BalanceType :: Active
328+ } else {
329+ staker. inactive_balance += value;
330+ if staker. inactive_from . is_none ( ) {
331+ staker. inactive_from = Some ( 0 ) ; // To uphold the (invariant 3)
332+ }
333+ BalanceType :: Inactive
334+ } ;
309335 self . balance += value;
310336
311- // Build the return logs
337+ // Create the receipt.
338+ let receipt = AddStakeReceipt {
339+ credited_balance : credited_balance. clone ( ) ,
340+ } ;
341+
342+ // If we are actively delegating to a validator, we need to update it.
343+ if credited_balance == BalanceType :: Active {
344+ if let Some ( validator_address) = & staker. delegation {
345+ self . increase_stake_to_validator ( store, validator_address, value) ;
346+ }
347+ }
348+
349+ // Build the return logs.
312350 tx_logger. push_log ( Log :: Stake {
313351 staker_address : staker_address. clone ( ) ,
314352 validator_address : staker. delegation . clone ( ) ,
315353 value,
354+ credited_balance,
316355 } ) ;
317356
318357 // Update the staker entry.
319358 store. put_staker ( staker_address, staker) ;
320359
321- Ok ( ( ) )
360+ Ok ( receipt )
322361 }
323362
324363 /// Reverts a stake transaction.
364+ /// IMPORTANT: This code is shared between new and legacy add stake versions.
325365 pub fn revert_add_stake (
326366 & mut self ,
327367 store : & mut StakingContractStoreWrite ,
328368 staker_address : & Address ,
329369 value : Coin ,
370+ receipt : AddStakeReceipt ,
330371 tx_logger : & mut TransactionLog ,
331372 ) -> Result < ( ) , AccountError > {
332373 // Get the staker.
333374 let mut staker = store. expect_staker ( staker_address) ?;
334375
335376 // If we are delegating to a validator, we need to update it too.
336- if let Some ( validator_address) = & staker. delegation {
337- self . decrease_stake_from_validator ( store, validator_address, value) ;
377+ if receipt. credited_balance == BalanceType :: Active {
378+ if let Some ( validator_address) = & staker. delegation {
379+ self . decrease_stake_from_validator ( store, validator_address, value) ;
380+ }
338381 }
339382
340383 // Update the staker's and staking contract's balances.
341- staker. active_balance -= value;
384+ match receipt. credited_balance {
385+ BalanceType :: Active => {
386+ staker. active_balance -= value;
387+ }
388+ BalanceType :: Inactive => {
389+ staker. inactive_balance -= value;
390+ if staker. inactive_balance . is_zero ( ) {
391+ staker. inactive_from = None ;
392+ }
393+ }
394+ BalanceType :: Retired => {
395+ staker. retired_balance -= value;
396+ }
397+ }
342398 self . balance -= value;
343399
344400 tx_logger. push_log ( Log :: Stake {
345401 staker_address : staker_address. clone ( ) ,
346402 validator_address : staker. delegation . clone ( ) ,
347403 value,
404+ credited_balance : receipt. credited_balance ,
348405 } ) ;
349406
350407 // Update the staker entry.
@@ -949,6 +1006,7 @@ impl StakingContract {
9491006 }
9501007
9511008 /// Adds `value` coins to a given validator's total stake.
1009+ /// IMPORTANT: This code is shared between new and legacy add stake versions.
9521010 fn increase_stake_to_validator (
9531011 & mut self ,
9541012 store : & mut StakingContractStoreWrite ,
@@ -982,6 +1040,7 @@ impl StakingContract {
9821040 }
9831041
9841042 /// Removes `value` coins from a given validator's inactive total stake.
1043+ /// IMPORTANT: This code is shared between new and legacy add stake versions.
9851044 fn decrease_stake_from_validator (
9861045 & mut self ,
9871046 store : & mut StakingContractStoreWrite ,
@@ -1015,4 +1074,57 @@ impl StakingContract {
10151074 // Neither validator nor tombstone exist, this is an error.
10161075 panic ! ( "inconsistent contract state" ) ;
10171076 }
1077+
1078+ // Pre protocol upgrade.
1079+ // IMPORTANT: DO NOT REMOVE THIS CODE!
1080+ // It is needed for history nodes to sync.
1081+
1082+ /// Legacy add stake logic, it adds more Coins to a staker's active balance.
1083+ /// It will be directly added to the staker's balance.
1084+ /// Anyone can add stake for a staker. The staker must already exist.
1085+ pub fn legacy_add_stake_0v (
1086+ & mut self ,
1087+ store : & mut StakingContractStoreWrite ,
1088+ staker_address : & Address ,
1089+ value : Coin ,
1090+ tx_logger : & mut TransactionLog ,
1091+ ) -> Result < AddStakeReceipt , AccountError > {
1092+ // Get the staker.
1093+ let mut staker = store. expect_staker ( staker_address) ?;
1094+
1095+ // Fail if the minimum stake would be violated for the non-retired funds (invariant 1).
1096+ Staker :: enforce_min_stake (
1097+ staker. active_balance + value,
1098+ staker. inactive_balance ,
1099+ staker. retired_balance ,
1100+ ) ?;
1101+
1102+ // All checks passed, not allowed to fail from here on!
1103+
1104+ // If we are delegating to a validator, we need to update it.
1105+ if let Some ( validator_address) = & staker. delegation {
1106+ // Check that the delegation is still valid, i.e. the validator hasn't been deleted.
1107+ store. expect_validator ( validator_address) ?;
1108+ self . increase_stake_to_validator ( store, validator_address, value) ;
1109+ }
1110+
1111+ // Update the staker's and staking contract's balances.
1112+ staker. active_balance += value;
1113+ self . balance += value;
1114+
1115+ // Build the return logs
1116+ tx_logger. push_log ( Log :: Stake {
1117+ staker_address : staker_address. clone ( ) ,
1118+ validator_address : staker. delegation . clone ( ) ,
1119+ value,
1120+ credited_balance : BalanceType :: Active ,
1121+ } ) ;
1122+
1123+ // Update the staker entry.
1124+ store. put_staker ( staker_address, staker) ;
1125+
1126+ Ok ( AddStakeReceipt {
1127+ credited_balance : BalanceType :: Active ,
1128+ } )
1129+ }
10181130}
0 commit comments