Repository: shardeum/shardeum
File: src/tx/staking/verifyStake.ts (TypeScript)

Code before:
      nomineeAccount.rewardStartTime > 0 &&
      !(unstakeCoinsTX.force && ShardeumFlags.allowForceUnstake)
    ) {
      //note that if both end time and start time are 0 it is ok to unstake
      success = false
      reason = `No reward endTime set, can't unstake node yet`
    }
  } else {
    success = false
    reason = `This nominee node is not found!`
  }

  if (!isStakeUnlocked(nominatorAccount, nomineeAccount, shardus)) {
    success = false
    reason = `The stake is not unlocked yet!`
  }

  return { success, reason }
}

function isStakeUnlocked(
  nominatorAccount: WrappedEVMAccount,
  nomineeAccount: NodeAccount2,
  shardus: Shardus
): boolean {
  const stakeLockTime = AccountsStorage.cachedNetworkAccount.current.stakeLockTime

  // SLT from time of last staking or unstaking
  if (shardus.shardusGetTime() - nominatorAccount.operatorAccountInfo.lastStakeTimestamp < stakeLockTime) {
    return false
  }

  // SLT from when node was selected to go active (started syncing)
  const node = shardus.getNodeByPubKey(nomineeAccount.id)
  if (node && shardus.shardusGetTime() - node.syncingTimestamp * 1000 < stakeLockTime) {
    return false
  }

  // SLT from time of last went active
  // THIS MAY BE REDUNDANT as we check for active status in verifyUnstakeTx
  if (shardus.shardusGetTime() - nomineeAccount.rewardStartTime * 1000 < stakeLockTime) {
    return false
  }

  // SLT from time of last went inactive/exit
  if (shardus.shardusGetTime() - nomineeAccount.rewardEndTime * 1000 < stakeLockTime) {
    return false
  }
}


Review comment (performance):
we shuold not use cachedNetworkAccount here anymore. instead we should use networkAccount from wrappedState as this function gets called from apply. reference: [https://linear.app/shm/issue/SHARD-41/todo-review-safety-of-using-cached-network-account-in-apply-functions](url)

Code after:
      nomineeAccount.rewardStartTime > 0 &&
      !(unstakeCoinsTX.force && ShardeumFlags.allowForceUnstake)
    ) {
      //note that if both end time and start time are 0 it is ok to unstake
      success = false
      reason = `No reward endTime set, can't unstake node yet`
    }
  } else {
    success = false
    reason = `This nominee node is not found!`
  }

  // eslint-disable-next-line security/detect-object-injection
  if (!isStakeUnlocked(nominatorAccount, nomineeAccount, shardus, wrappedStates[globalAccount].data)) {
    success = false
    reason = `The stake is not unlocked yet!`
  }

  return { success, reason }
}

function isStakeUnlocked(
  nominatorAccount: WrappedEVMAccount,
  nomineeAccount: NodeAccount2,
  shardus: Shardus,
  networkAccount: NetworkAccount
): boolean {
  const stakeLockTime = networkAccount.current.stakeLockTime

  // SLT from time of last staking or unstaking
  if (shardus.shardusGetTime() - nominatorAccount.operatorAccountInfo.lastStakeTimestamp < stakeLockTime) {
    return false
  }

  // SLT from when node was selected to go active (started syncing)
  const node = shardus.getNodeByPubKey(nomineeAccount.id)
  if (node && shardus.shardusGetTime() - node.syncingTimestamp * 1000 < stakeLockTime) {
    return false
  }

  // SLT from time of last went active
  // THIS MAY BE REDUNDANT as we check for active status in verifyUnstakeTx
  if (shardus.shardusGetTime() - nomineeAccount.rewardStartTime * 1000 < stakeLockTime) {
    return false
  }

  // SLT from time of last went inactive/exit
  if (shardus.shardusGetTime() - nomineeAccount.rewardEndTime * 1000 < stakeLockTime) {
    return false
  }
}