diff --git a/audits/internal4/README.md b/audits/internal4/README.md
index d916194f..dd3709ea 100644
--- a/audits/internal4/README.md
+++ b/audits/internal4/README.md
@@ -210,5 +210,58 @@ The review has been performed based on the contract code in the following reposi
`https://github.com/valory-xyz/autonolas-registries`
commit: `v1.1.7.pre-internal-audit` or `387ba93deeb36849c1c205711b97a2c6da0f2745`
Re-audit after extract Mech contracts into a separate repo.
+[slither-full-25-10-23](https://github.com/valory-xyz/autonolas-registries/blob/main/audits/internal4/analysis/slither_full_25_10_23.txt)
I don't see any new problems after optimization.
+
+### Security issues. Updated 29-11-23
+The review has been performed based on the contract code in the following repository:
+`https://github.com/valory-xyz/autonolas-registries`
+commit: `tag: v1.1.8-pre-internal-audit` or `99eef0ffd2450311b2770faceac37191a13af5cb`
+Re-audit after changes ServiceStakingBase.
+[slither-full](https://github.com/valory-xyz/autonolas-registries/blob/main/audits/internal4/analysis/slither_full.txt)
+
+#### Notes (critical improvements)
+Due to the impossibility of manually assessing the correctness of the logic at the boundaries of ```_evict```
+"evict" must be part of ServiceStaking.t.sol (fuzzing)
+or equivalent in js tests
+Clarification required on the source code of the tests:
+```solidity
+We need cases:
+numEvictServices == 1
+numEvictServices > 1
+numEvictServices == setServiceIds
+numEvictServices < setServiceIds
+
++ is it possible to exclude a special case
+ // Deal with the very first element
+ // Get the evicted service index
+ idx = serviceIndexes[0];
+ // Assign last service Id to the index that points to the evicted service Id
+ setServiceIds[idx] = setServiceIds[totalNumServices - 1];
+ // Pop the last element
+ setServiceIds.pop();
+ and move it under for()
+```
+
+#### Notes (unstake scenario)
+```solidity
+ // Check that the service has staked long enough, or if there are no rewards left
+ uint256 ts = block.timestamp - tsStart;
+ if (ts <= maxAllowedInactivity && availableRewards > 0) {
+ revert NotEnoughTimeStaked(serviceId, ts, maxAllowedInactivity);
+ }
+
+ 1. Service staking
+ 2. Service inactive all time // if (serviceInactivity[i] > maxAllowedInactivity)
+ 2a. Service evict // _evict(evictServiceIds, serviceInactivity, numServices);
+ 3. Service try unstake in 1s after 2a. // => tsStart = sInfo.tsStart; ts = block.timestamp - tsStart;
+ ??? What will happen?
+
+ What is the expected scenario for this barrier?
+ // Check that the service has staked long enough, or if there are no rewards left
+ uint256 ts = block.timestamp - tsStart;
+ if (ts <= maxAllowedInactivity && availableRewards > 0) {
+ revert NotEnoughTimeStaked(serviceId, ts, maxAllowedInactivity);
+ }
+```
diff --git a/audits/internal4/analysis/contracts/ServiceStakingBase-flatten.sol b/audits/internal4/analysis/contracts/ServiceStakingBase-flatten.sol
index a9580b50..50095c1a 100644
--- a/audits/internal4/analysis/contracts/ServiceStakingBase-flatten.sol
+++ b/audits/internal4/analysis/contracts/ServiceStakingBase-flatten.sol
@@ -4,7 +4,6 @@
// File contracts/interfaces/IErrorsRegistries.sol
-// Original license: SPDX_License_Identifier: MIT
pragma solidity ^0.8.21;
/// @dev Errors.
@@ -422,9 +421,19 @@ error LowerThan(uint256 provided, uint256 expected);
/// @param serviceId Service Id.
error WrongServiceConfiguration(uint256 serviceId);
-/// @dev Service is not staked.
+/// @dev Service is not unstaked.
/// @param serviceId Service Id.
-error ServiceNotStaked(uint256 serviceId);
+error ServiceNotUnstaked(uint256 serviceId);
+
+/// @dev Service is not found.
+/// @param serviceId Service Id.
+error ServiceNotFound(uint256 serviceId);
+
+/// @dev Service was not staked a minimum required time.
+/// @param serviceId Service Id.
+/// @param tsProvided Time the service is staked for.
+/// @param tsExpected Minimum time the service needs to be staked for.
+error NotEnoughTimeStaked(uint256 serviceId, uint256 tsProvided, uint256 tsExpected);
// Service Info struct
struct ServiceInfo {
@@ -438,6 +447,8 @@ struct ServiceInfo {
uint256 tsStart;
// Accumulated service staking reward
uint256 reward;
+ // Accumulated inactivity that might lead to the service eviction
+ uint256 inactivity;
}
/// @title ServiceStakingBase - Base abstract smart contract for staking a service by its owner
@@ -445,6 +456,12 @@ struct ServiceInfo {
/// @author Andrey Lebedev -
/// @author Mariapia Moscatiello -
abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
+ enum ServiceStakingState {
+ Unstaked,
+ Staked,
+ Evicted
+ }
+
// Input staking parameters
struct StakingParams {
// Maximum number of staking services
@@ -453,6 +470,8 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
uint256 rewardsPerSecond;
// Minimum service staking deposit value required for staking
uint256 minStakingDeposit;
+ // Max number of accumulated inactivity periods after which the service is evicted
+ uint256 maxNumInactivityPeriods;
// Liveness period
uint256 livenessPeriod;
// Liveness ratio in the format of 1e18
@@ -467,10 +486,13 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
bytes32 configHash;
}
- event ServiceStaked(uint256 indexed serviceId, address indexed owner, address indexed multisig, uint256[] nonces);
- event Checkpoint(uint256 availableRewards, uint256 numServices);
- event ServiceUnstaked(uint256 indexed serviceId, address indexed owner, address indexed multisig, uint256[] nonces,
- uint256 reward, uint256 tsStart);
+ event ServiceStaked(uint256 epoch, uint256 indexed serviceId, address indexed owner, address indexed multisig,
+ uint256[] nonces);
+ event Checkpoint(uint256 indexed epoch, uint256 availableRewards, uint256[] serviceIds, uint256[] rewards);
+ event ServiceUnstaked(uint256 epoch, uint256 indexed serviceId, address indexed owner, address indexed multisig,
+ uint256[] nonces, uint256 reward);
+ event ServicesEvicted(uint256 indexed epoch, uint256[] serviceIds, address[] owners, address[] multisigs,
+ uint256[] serviceInactivity);
event Deposit(address indexed sender, uint256 amount, uint256 balance, uint256 availableRewards);
event Withdraw(address indexed to, uint256 amount);
@@ -483,6 +505,8 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
// Minimum service staking deposit value required for staking
// The staking deposit must be always greater than 1 in order to distinguish between native and ERC20 tokens
uint256 public immutable minStakingDeposit;
+ // Max number of accumulated inactivity periods after which the service is evicted
+ uint256 public immutable maxNumInactivityPeriods;
// Liveness period
uint256 public immutable livenessPeriod;
// Liveness ratio in the format of 1e18
@@ -497,7 +521,11 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
address public immutable serviceRegistry;
// Approved multisig proxy hash
bytes32 public immutable proxyHash;
+ // Max allowed inactivity
+ uint256 public immutable maxAllowedInactivity;
+ // Epoch counter
+ uint256 public epochCounter;
// Token / ETH balance
uint256 public balance;
// Token / ETH available rewards
@@ -519,7 +547,7 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
// Initial checks
if (_stakingParams.maxNumServices == 0 || _stakingParams.rewardsPerSecond == 0 ||
_stakingParams.livenessPeriod == 0 || _stakingParams.livenessRatio == 0 ||
- _stakingParams.numAgentInstances == 0) {
+ _stakingParams.numAgentInstances == 0 || _stakingParams.maxNumInactivityPeriods == 0) {
revert ZeroValue();
}
if (_stakingParams.minStakingDeposit < 2) {
@@ -533,6 +561,7 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
maxNumServices = _stakingParams.maxNumServices;
rewardsPerSecond = _stakingParams.rewardsPerSecond;
minStakingDeposit = _stakingParams.minStakingDeposit;
+ maxNumInactivityPeriods = _stakingParams.maxNumInactivityPeriods;
livenessPeriod = _stakingParams.livenessPeriod;
livenessRatio = _stakingParams.livenessRatio;
numAgentInstances = _stakingParams.numAgentInstances;
@@ -561,6 +590,9 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
// Record provided multisig proxy bytecode hash
proxyHash = _proxyHash;
+ // Calculate max allowed inactivity
+ maxAllowedInactivity = _stakingParams.maxNumInactivityPeriods * livenessPeriod;
+
// Set the checkpoint timestamp to be the deployment one
tsCheckpoint = block.timestamp;
}
@@ -580,6 +612,8 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
function _withdraw(address to, uint256 amount) internal virtual;
/// @dev Stakes the service.
+ /// @notice Each service must be staked for a minimum of maxAllowedInactivity time, or until the funds are not zero.
+ /// maxAllowedInactivity = maxNumInactivityPeriods * livenessPeriod
/// @param serviceId Service Id.
function stake(uint256 serviceId) external {
// Check if there available rewards
@@ -587,6 +621,13 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
revert NoRewardsAvailable();
}
+ // Check if the evicted service has not yet unstaked
+ ServiceInfo storage sInfo = mapServiceInfo[serviceId];
+ // tsStart being greater than zero means that the service was not yet unstaked: still staking or evicted
+ if (sInfo.tsStart > 0) {
+ revert ServiceNotUnstaked(serviceId);
+ }
+
// Check for the maximum number of staking services
uint256 numStakingServices = setServiceIds.length;
if (numStakingServices == maxNumServices) {
@@ -639,7 +680,6 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
_checkTokenStakingDeposit(serviceId, service.securityDeposit);
// ServiceInfo struct will be an empty one since otherwise the safeTransferFrom above would fail
- ServiceInfo storage sInfo = mapServiceInfo[serviceId];
sInfo.multisig = service.multisig;
sInfo.owner = msg.sender;
uint256[] memory nonces = _getMultisigNonces(service.multisig);
@@ -652,7 +692,7 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
// Transfer the service for staking
IService(serviceRegistry).safeTransferFrom(msg.sender, address(this), serviceId);
- emit ServiceStaked(serviceId, msg.sender, service.multisig, nonces);
+ emit ServiceStaked(epochCounter, serviceId, msg.sender, service.multisig, nonces);
}
/// @dev Gets service multisig nonces.
@@ -694,6 +734,7 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
/// @param eligibleServiceRewards Corresponding rewards for eligible service Ids.
/// @param serviceIds All the staking service Ids.
/// @param serviceNonces Current service nonces.
+ /// @param serviceInactivity Service inactivity records.
function _calculateStakingRewards() internal view returns (
uint256 lastAvailableRewards,
uint256 numServices,
@@ -701,7 +742,8 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
uint256[] memory eligibleServiceIds,
uint256[] memory eligibleServiceRewards,
uint256[] memory serviceIds,
- uint256[][] memory serviceNonces
+ uint256[][] memory serviceNonces,
+ uint256[] memory serviceInactivity
)
{
// Check the last checkpoint timestamp and the liveness period, also check for available rewards to be not zero
@@ -716,6 +758,7 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
eligibleServiceIds = new uint256[](size);
eligibleServiceRewards = new uint256[](size);
serviceNonces = new uint256[][](size);
+ serviceInactivity = new uint256[](size);
// Calculate each staked service reward eligibility
for (uint256 i = 0; i < size; ++i) {
@@ -738,47 +781,116 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
}
// Calculate the liveness ratio in 1e18 value
- // This subtraction is always positive or zero, as the last checkpoint can be at most block.timestamp
+ // This subtraction is always positive or zero, as the last checkpoint is at most block.timestamp
ts = block.timestamp - serviceCheckpoint;
bool ratioPass = _isRatioPass(serviceNonces[i], sInfo.nonces, ts);
// Record the reward for the service if it has provided enough transactions
if (ratioPass) {
// Calculate the reward up until now and record its value for the corresponding service
- uint256 reward = rewardsPerSecond * ts;
- totalRewards += reward;
- eligibleServiceRewards[numServices] = reward;
+ eligibleServiceRewards[numServices] = rewardsPerSecond * ts;
+ totalRewards += eligibleServiceRewards[numServices];
eligibleServiceIds[numServices] = serviceIds[i];
++numServices;
+ } else {
+ serviceInactivity[i] = ts;
}
}
}
}
+ /// @dev Evicts services due to their extended inactivity.
+ /// @param evictServiceIds Service Ids to be evicted.
+ /// @param serviceInactivity Corresponding service inactivity records.
+ /// @param numEvictServices Number of services to evict.
+ function _evict(
+ uint256[] memory evictServiceIds,
+ uint256[] memory serviceInactivity,
+ uint256 numEvictServices
+ ) internal
+ {
+ // Get the total number of staked services
+ // All the passed arrays have the length of the number of staked services
+ uint256 totalNumServices = evictServiceIds.length;
+
+ // Get arrays of exact sizes
+ uint256[] memory serviceIds = new uint256[](numEvictServices);
+ address[] memory owners = new address[](numEvictServices);
+ address[] memory multisigs = new address[](numEvictServices);
+ uint256[] memory inactivity = new uint256[](numEvictServices);
+ uint256[] memory serviceIndexes = new uint256[](numEvictServices);
+
+ // Fill in arrays
+ uint256 sCounter;
+ uint256 serviceId;
+ for (uint256 i = 0; i < totalNumServices; ++i) {
+ if (evictServiceIds[i] > 0) {
+ serviceId = evictServiceIds[i];
+ serviceIds[sCounter] = serviceId;
+
+ ServiceInfo storage sInfo = mapServiceInfo[serviceId];
+ owners[sCounter] = sInfo.owner;
+ multisigs[sCounter] = sInfo.multisig;
+ inactivity[sCounter] = serviceInactivity[i];
+ serviceIndexes[sCounter] = i;
+ sCounter++;
+ }
+ }
+
+ // Evict services from the global set of staked services
+ uint256 idx;
+ for (uint256 i = numEvictServices - 1; i > 0; --i) {
+ // Decrease the number of services
+ totalNumServices--;
+ // Get the evicted service index
+ idx = serviceIndexes[i];
+ // Assign last service Id to the index that points to the evicted service Id
+ setServiceIds[idx] = setServiceIds[totalNumServices];
+ // Pop the last element
+ setServiceIds.pop();
+ }
+
+ // Deal with the very first element
+ // Get the evicted service index
+ idx = serviceIndexes[0];
+ // Assign last service Id to the index that points to the evicted service Id
+ setServiceIds[idx] = setServiceIds[totalNumServices - 1];
+ // Pop the last element
+ setServiceIds.pop();
+
+ emit ServicesEvicted(epochCounter, serviceIds, owners, multisigs, inactivity);
+ }
+
/// @dev Checkpoint to allocate rewards up until a current time.
- /// @return All staking service Ids.
- /// @return All staking updated nonces.
- /// @return Number of reward-eligible staking services during current checkpoint period.
- /// @return Eligible service Ids.
- /// @return Eligible service rewards.
- /// @return success True, if the checkpoint was successful.
+ /// @return All staking service Ids (including evicted ones within a current epoch).
+ /// @return All staking updated nonces (including evicted ones within a current epoch).
+ /// @return Set of reward-eligible service Ids.
+ /// @return Corresponding set of reward-eligible service rewards.
+ /// @return evictServiceIds Evicted service Ids.
function checkpoint() public returns (
uint256[] memory,
uint256[][] memory,
- uint256,
uint256[] memory,
uint256[] memory,
- bool success
+ uint256[] memory evictServiceIds
)
{
// Calculate staking rewards
(uint256 lastAvailableRewards, uint256 numServices, uint256 totalRewards,
uint256[] memory eligibleServiceIds, uint256[] memory eligibleServiceRewards,
- uint256[] memory serviceIds, uint256[][] memory serviceNonces) = _calculateStakingRewards();
+ uint256[] memory serviceIds, uint256[][] memory serviceNonces,
+ uint256[] memory serviceInactivity) = _calculateStakingRewards();
+
+ // Get arrays for eligible service Ids and rewards of exact size
+ uint256[] memory finalEligibleServiceIds;
+ uint256[] memory finalEligibleServiceRewards;
+ evictServiceIds = new uint256[](serviceIds.length);
+ uint256 curServiceId;
// If there are eligible services, proceed with staking calculation and update rewards
if (numServices > 0) {
- uint256 curServiceId;
+ finalEligibleServiceIds = new uint256[](numServices);
+ finalEligibleServiceRewards = new uint256[](numServices);
// If total allocated rewards are not enough, adjust the reward value
if (totalRewards > lastAvailableRewards) {
// Traverse all the eligible services and adjust their rewards proportional to leftovers
@@ -791,6 +903,8 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
updatedTotalRewards += updatedReward;
// Add reward to the overall service reward
curServiceId = eligibleServiceIds[i];
+ finalEligibleServiceIds[i] = eligibleServiceIds[i];
+ finalEligibleServiceRewards[i] = updatedReward;
mapServiceInfo[curServiceId].reward += updatedReward;
}
@@ -798,10 +912,12 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
updatedReward = (eligibleServiceRewards[0] * lastAvailableRewards) / totalRewards;
updatedTotalRewards += updatedReward;
curServiceId = eligibleServiceIds[0];
+ finalEligibleServiceIds[0] = eligibleServiceIds[0];
// If the reward adjustment happened to have small leftovers, add it to the first service
if (lastAvailableRewards > updatedTotalRewards) {
updatedReward += lastAvailableRewards - updatedTotalRewards;
}
+ finalEligibleServiceRewards[0] = updatedReward;
// Add reward to the overall service reward
mapServiceInfo[curServiceId].reward += updatedReward;
// Set available rewards to zero
@@ -811,6 +927,8 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
for (uint256 i = 0; i < numServices; ++i) {
// Add reward to the service overall reward
curServiceId = eligibleServiceIds[i];
+ finalEligibleServiceIds[i] = eligibleServiceIds[i];
+ finalEligibleServiceRewards[i] = eligibleServiceRewards[i];
mapServiceInfo[curServiceId].reward += eligibleServiceRewards[i];
}
@@ -822,24 +940,50 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
availableRewards = lastAvailableRewards;
}
- // If service nonces were updated, then the checkpoint takes place, otherwise only service Ids are returned
- if (serviceNonces.length > 0) {
- // Updated current service nonces
+ // If service Ids are returned, then the checkpoint takes place
+ if (serviceIds.length > 0) {
+ numServices = 0;
+ // Record service inactivities and updated current service nonces
for (uint256 i = 0; i < serviceIds.length; ++i) {
// Get the current service Id
- uint256 curServiceId = serviceIds[i];
+ curServiceId = serviceIds[i];
+ // Record service nonces
mapServiceInfo[curServiceId].nonces = serviceNonces[i];
+
+ // Increase service inactivity if it is greater than zero
+ if (serviceInactivity[i] > 0) {
+ // Get the overall continuous service inactivity
+ serviceInactivity[i] = mapServiceInfo[curServiceId].inactivity + serviceInactivity[i];
+ mapServiceInfo[curServiceId].inactivity = serviceInactivity[i];
+ // Check for the maximum allowed inactivity time
+ if (serviceInactivity[i] > maxAllowedInactivity) {
+ // Evict a service if it has been inactive for more than a maximum allowed inactivity time
+ evictServiceIds[i] = curServiceId;
+ // Increase number of evicted services
+ numServices++;
+ }
+ } else {
+ // Otherwise, set it back to zero
+ mapServiceInfo[curServiceId].inactivity = 0;
+ }
+ }
+
+ // Evict inactive services
+ if (numServices > 0) {
+ _evict(evictServiceIds, serviceInactivity, numServices);
}
// Record the current timestamp such that next calculations start from this point of time
tsCheckpoint = block.timestamp;
- success = true;
+ // Increase the epoch counter
+ uint256 eCounter = epochCounter;
+ epochCounter = eCounter + 1;
- emit Checkpoint(lastAvailableRewards, numServices);
+ emit Checkpoint(eCounter, lastAvailableRewards, finalEligibleServiceIds, finalEligibleServiceRewards);
}
- return (serviceIds, serviceNonces, numServices, eligibleServiceIds, eligibleServiceRewards, success);
+ return (serviceIds, serviceNonces, finalEligibleServiceIds, finalEligibleServiceRewards, evictServiceIds);
}
/// @dev Unstakes the service.
@@ -851,19 +995,36 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
revert OwnerOnly(msg.sender, sInfo.owner);
}
+ // Get the staking start time
+ // Note that if the service info exists, the service is staked or evicted, and thus start time is always valid
+ uint256 tsStart = sInfo.tsStart;
+
+ // Check that the service has staked long enough, or if there are no rewards left
+ uint256 ts = block.timestamp - tsStart;
+ if (ts <= maxAllowedInactivity && availableRewards > 0) {
+ revert NotEnoughTimeStaked(serviceId, ts, maxAllowedInactivity);
+ }
+
// Call the checkpoint
- (uint256[] memory serviceIds, , , , , bool success) = checkpoint();
+ (uint256[] memory serviceIds, , , , uint256[] memory evictServiceIds) = checkpoint();
// If the checkpoint was not successful, the serviceIds set is not returned and needs to be allocated
- if (!success) {
+ if (serviceIds.length == 0) {
serviceIds = getServiceIds();
+ evictServiceIds = new uint256[](serviceIds.length);
}
// Get the service index in the set of services
// The index must always exist as the service is currently staked, otherwise it has no record in the map
uint256 idx;
+ bool inSet;
for (; idx < serviceIds.length; ++idx) {
- if (serviceIds[idx] == serviceId) {
+ // Service is still in a global staking set if it is found in the services set,
+ // and is not present in the evicted set
+ if (evictServiceIds[idx] == serviceId) {
+ break;
+ } else if (serviceIds[idx] == serviceId) {
+ inSet = true;
break;
}
}
@@ -871,7 +1032,6 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
// Get the unstaked service data
uint256 reward = sInfo.reward;
uint256[] memory nonces = sInfo.nonces;
- uint256 tsStart = sInfo.tsStart;
address multisig = sInfo.multisig;
// Clear all the data about the unstaked service
@@ -879,8 +1039,11 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
delete mapServiceInfo[serviceId];
// Update the set of staked service Ids
- setServiceIds[idx] = setServiceIds[setServiceIds.length - 1];
- setServiceIds.pop();
+ // If the index was not found, the service was evicted and is not part of staked services set
+ if (inSet) {
+ setServiceIds[idx] = setServiceIds[setServiceIds.length - 1];
+ setServiceIds.pop();
+ }
// Transfer the service back to the owner
IService(serviceRegistry).safeTransferFrom(address(this), msg.sender, serviceId);
@@ -890,25 +1053,16 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
_withdraw(multisig, reward);
}
- emit ServiceUnstaked(serviceId, msg.sender, multisig, nonces, reward, tsStart);
+ emit ServiceUnstaked(epochCounter, serviceId, msg.sender, multisig, nonces, reward);
}
- /// @dev Calculates service staking reward at current timestamp.
+ /// @dev Calculates service staking reward during the last checkpoint period.
/// @param serviceId Service Id.
/// @return reward Service reward.
- function calculateServiceStakingReward(uint256 serviceId) external view returns (uint256 reward) {
- // Get current service reward
- ServiceInfo memory sInfo = mapServiceInfo[serviceId];
- reward = sInfo.reward;
-
- // Check if the service is staked
- if (sInfo.tsStart == 0) {
- revert ServiceNotStaked(serviceId);
- }
-
+ function calculateServiceStakingLastReward(uint256 serviceId) public view returns (uint256 reward) {
// Calculate overall staking rewards
(uint256 lastAvailableRewards, uint256 numServices, uint256 totalRewards, uint256[] memory eligibleServiceIds,
- uint256[] memory eligibleServiceRewards, , ) = _calculateStakingRewards();
+ uint256[] memory eligibleServiceRewards, , , ) = _calculateStakingRewards();
// If there are eligible services, proceed with staking calculation and update rewards for the service Id
for (uint256 i = 0; i < numServices; ++i) {
@@ -916,33 +1070,37 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
if (eligibleServiceIds[i] == serviceId) {
// If total allocated rewards are not enough, adjust the reward value
if (totalRewards > lastAvailableRewards) {
- reward += (eligibleServiceRewards[i] * lastAvailableRewards) / totalRewards;
+ reward = (eligibleServiceRewards[i] * lastAvailableRewards) / totalRewards;
} else {
- reward += eligibleServiceRewards[i];
+ reward = eligibleServiceRewards[i];
}
break;
}
}
}
- /// @dev Gets staked service Ids.
- /// @return serviceIds Staked service Ids.
- function getServiceIds() public view returns (uint256[] memory serviceIds) {
- // Get the number of service Ids
- uint256 size = setServiceIds.length;
- serviceIds = new uint256[](size);
+ /// @dev Calculates overall service staking reward at current timestamp.
+ /// @param serviceId Service Id.
+ /// @return reward Service reward.
+ function calculateServiceStakingReward(uint256 serviceId) external view returns (uint256 reward) {
+ // Get current service reward
+ ServiceInfo memory sInfo = mapServiceInfo[serviceId];
+ reward = sInfo.reward;
- // Record service Ids
- for (uint256 i = 0; i < size; ++i) {
- serviceIds[i] = setServiceIds[i];
- }
+ // Add pending reward
+ reward += calculateServiceStakingLastReward(serviceId);
}
- /// @dev Checks if the service is staked.
+ /// @dev Gets the service staking state.
/// @param serviceId.
- /// @return isStaked True, if the service is staked.
- function isServiceStaked(uint256 serviceId) external view returns (bool isStaked) {
- isStaked = (mapServiceInfo[serviceId].tsStart > 0);
+ /// @return stakingState Staking state of the service.
+ function getServiceStakingState(uint256 serviceId) external view returns (ServiceStakingState stakingState) {
+ ServiceInfo memory sInfo = mapServiceInfo[serviceId];
+ if (sInfo.inactivity > maxAllowedInactivity) {
+ stakingState = ServiceStakingState.Evicted;
+ } else if (sInfo.tsStart > 0) {
+ stakingState = ServiceStakingState.Staked;
+ }
}
/// @dev Gets the next reward checkpoint timestamp.
@@ -951,4 +1109,23 @@ abstract contract ServiceStakingBase is ERC721TokenReceiver, IErrorsRegistries {
// Last checkpoint timestamp plus the liveness period
tsNext = tsCheckpoint + livenessPeriod;
}
+
+ /// @dev Gets staked service info.
+ /// @param serviceId Service Id.
+ /// @return sInfo Struct object with the corresponding service info.
+ function getServiceInfo(uint256 serviceId) external view returns (ServiceInfo memory sInfo) {
+ sInfo = mapServiceInfo[serviceId];
+ }
+
+ /// @dev Gets staked service Ids.
+ /// @return Staked service Ids.
+ function getServiceIds() public view returns (uint256[] memory) {
+ return setServiceIds;
+ }
+
+ /// @dev Gets canonical agent Ids from the service configuration.
+ /// @return Agent Ids.
+ function getAgentIds() external view returns (uint256[] memory) {
+ return agentIds;
+ }
}
diff --git a/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.ERC721.call-graph.png b/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.ERC721.call-graph.png
index 5c9205e4..5398b809 100644
Binary files a/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.ERC721.call-graph.png and b/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.ERC721.call-graph.png differ
diff --git a/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.ServiceStakingBase.call-graph.png b/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.ServiceStakingBase.call-graph.png
index 654e625a..d8e1993d 100644
Binary files a/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.ServiceStakingBase.call-graph.png and b/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.ServiceStakingBase.call-graph.png differ
diff --git a/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.all_contracts.call-graph.png b/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.all_contracts.call-graph.png
index 579438f6..ffdfd42e 100644
Binary files a/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.all_contracts.call-graph.png and b/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.all_contracts.call-graph.png differ
diff --git a/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.inheritance-graph.png b/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.inheritance-graph.png
index c368042d..1ee62916 100644
Binary files a/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.inheritance-graph.png and b/audits/internal4/analysis/slither_ServiceStakingBase-flatten.sol.inheritance-graph.png differ
diff --git a/audits/internal4/analysis/slither_full.txt b/audits/internal4/analysis/slither_full.txt
index f1900b80..db1a28dd 100644
--- a/audits/internal4/analysis/slither_full.txt
+++ b/audits/internal4/analysis/slither_full.txt
@@ -1,283 +1,84 @@
-Notes: False positive.
-INFO:Detectors:
-ServiceStakingBase.calculateServiceStakingReward(uint256) (ServiceStakingNativeToken-flatten.sol#899-926) uses a dangerous strict equality:
- - sInfo.tsStart == 0 (ServiceStakingNativeToken-flatten.sol#905)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities
-
-Notes: False positive.
-INFO:Detectors:
-ServiceStakingBase unstake parameter from is not related to msg.sender IService(serviceRegistry).safeTransferFrom(address(this),msg.sender,serviceId) (ServiceStakingNativeToken-flatten.sol#886)
-Reference: https://ventral.digital/posts/2022/8/18/sznsdaos-bountyboard-unauthorized-transferfrom-vulnerability
-
-Notes: False positive.
-INFO:Detectors:
-Function ServiceStakingNativeToken._withdraw(address,uint256) (ServiceStakingNativeToken-flatten.sol#975-984) contains a low level call to a custom address
-Reference: https://github.com/pessimistic-io/slitherin/blob/master/docs/call_forward_to_protected.md
-
-Notes: False positive.
-INFO:Detectors:
-Reentrancy in ServiceStakingBase.stake(uint256) (ServiceStakingNativeToken-flatten.sol#584-656):
- External calls:
- - IService(serviceRegistry).safeTransferFrom(msg.sender,address(this),serviceId) (ServiceStakingNativeToken-flatten.sol#653)
- Event emitted after the call(s):
- - ServiceStaked(serviceId,msg.sender,service.multisig,nonces) (ServiceStakingNativeToken-flatten.sol#655)
-Reentrancy in ServiceStakingBase.unstake(uint256) (ServiceStakingNativeToken-flatten.sol#847-894):
- External calls:
- - IService(serviceRegistry).safeTransferFrom(address(this),msg.sender,serviceId) (ServiceStakingNativeToken-flatten.sol#886)
- Event emitted after the call(s):
- - ServiceUnstaked(serviceId,msg.sender,multisig,nonces,reward,tsStart) (ServiceStakingNativeToken-flatten.sol#893)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3
-
-Notes: False positive.
-INFO:Detectors:
-ServiceStakingBase._isRatioPass(uint256[],uint256[],uint256) (ServiceStakingNativeToken-flatten.sol#675-687) uses timestamp for comparisons
- Dangerous comparisons:
- - ts > 0 && curNonces[0] > lastNonces[0] (ServiceStakingNativeToken-flatten.sol#683)
- - ratioPass = (ratio >= livenessRatio) (ServiceStakingNativeToken-flatten.sol#685)
-ServiceStakingBase._calculateStakingRewards() (ServiceStakingNativeToken-flatten.sol#697-756) uses timestamp for comparisons
- Dangerous comparisons:
- - block.timestamp - tsCheckpointLast >= livenessPeriod && lastAvailableRewards > 0 (ServiceStakingNativeToken-flatten.sol#710)
- - ts > serviceCheckpoint (ServiceStakingNativeToken-flatten.sol#736)
-ServiceStakingBase.calculateServiceStakingReward(uint256) (ServiceStakingNativeToken-flatten.sol#899-926) uses timestamp for comparisons
- Dangerous comparisons:
- - sInfo.tsStart == 0 (ServiceStakingNativeToken-flatten.sol#905)
-ServiceStakingBase.isServiceStaked(uint256) (ServiceStakingNativeToken-flatten.sol#944-946) uses timestamp for comparisons
- Dangerous comparisons:
- - isStaked = (mapServiceInfo[serviceId].tsStart > 0) (ServiceStakingNativeToken-flatten.sol#945)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp
-
-Notes: False positive.
-INFO:Detectors:
-ServiceStakingBase.stake(uint256) (ServiceStakingNativeToken-flatten.sol#584-656) has a high cyclomatic complexity (12).
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity
-
-Notes: False positive.
-INFO:Detectors:
-ERC721._burn(uint256) (ServiceStakingNativeToken-flatten.sol#296-311) is never used and should be removed
-ERC721._mint(address,uint256) (ServiceStakingNativeToken-flatten.sol#281-294) is never used and should be removed
-ERC721._safeMint(address,uint256) (ServiceStakingNativeToken-flatten.sol#317-326) is never used and should be removed
-ERC721._safeMint(address,uint256,bytes) (ServiceStakingNativeToken-flatten.sol#328-341) is never used and should be removed
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code
-
-Notes: False positive.
-INFO:Detectors:
-Low level call in ServiceStakingNativeToken._withdraw(address,uint256) (ServiceStakingNativeToken-flatten.sol#975-984):
- - (result) = to.call{value: amount}() (ServiceStakingNativeToken-flatten.sol#980)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls
-
-Notes: False positive.
-INFO:Detectors:
-Variable ERC721._ownerOf (ServiceStakingNativeToken-flatten.sol#155) is not in mixedCase
-Variable ERC721._balanceOf (ServiceStakingNativeToken-flatten.sol#157) is not in mixedCase
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions
-
-Notes: False positive.
-INFO:Detectors:
-ERC721 (ServiceStakingNativeToken-flatten.sol#130-342) does not implement functions:
- - ERC721.tokenURI(uint256) (ServiceStakingNativeToken-flatten.sol#149)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions
-INFO:Detectors:
-
-Notes: False positive.
-Manipulated call found: (success) = multisig.call(payload) (GnosisSafeSameAddressMultisig-flatten.sol#124) in GnosisSafeSameAddressMultisig.create(address[],uint256,bytes) (GnosisSafeSameAddressMultisig-flatten.sol#91-150)
-Both destination and calldata could be manipulated
- The call could be fully manipulated (arbitrary call) through GnosisSafeSameAddressMultisig.create(address[],uint256,bytes) (GnosisSafeSameAddressMultisig-flatten.sol#91-150)
-Reference: https://github.com/pessimistic-io/slitherin/blob/master/docs/arbitrary_call.md
-
-Notes: False positive.
-INFO:Detectors:
-GnosisSafeSameAddressMultisig.create(address[],uint256,bytes).multisig (GnosisSafeSameAddressMultisig-flatten.sol#95) lacks a zero-check on :
- - (success) = multisig.call(payload) (GnosisSafeSameAddressMultisig-flatten.sol#124)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation
-
-Notes: False positive.
-INFO:Detectors:
-GnosisSafeSameAddressMultisig.create(address[],uint256,bytes) (GnosisSafeSameAddressMultisig-flatten.sol#91-150) uses assembly
- - INLINE ASM (GnosisSafeSameAddressMultisig-flatten.sol#104-106)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage
-
-Notes: False positive.
-INFO:Detectors:
-Low level call in GnosisSafeSameAddressMultisig.create(address[],uint256,bytes) (GnosisSafeSameAddressMultisig-flatten.sol#91-150):
- - (success) = multisig.call(payload) (GnosisSafeSameAddressMultisig-flatten.sol#124)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls
-Notes: False positive.
-INFO:Detectors:
-In a function GnosisSafeSameAddressMultisig.create(address[],uint256,bytes) (GnosisSafeSameAddressMultisig-flatten.sol#91-150) variable GnosisSafeSameAddressMultisig.DEFAULT_DATA_LENGTH (GnosisSafeSameAddressMultisig-flatten.sol#59) is read multiple times
-Reference: https://github.com/pessimistic-io/slitherin/blob/master/docs/multiple_storage_read.md
-INFO:Detectors:
-IToken (ServiceStakingToken-flatten.sol#960-1003) has incorrect ERC721 function interface:IToken.approve(address,uint256) (ServiceStakingToken-flatten.sol#991)
-IToken (ServiceStakingToken-flatten.sol#960-1003) has incorrect ERC721 function interface:IToken.transferFrom(address,address,uint256) (ServiceStakingToken-flatten.sol#998)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface
+Notes: false positive
+ServiceStakingBase.checkpoint().finalEligibleServiceRewards (ServiceStakingBase-flatten.sol#896) is a local variable never initialized
+ServiceStakingBase.checkpoint().finalEligibleServiceIds (ServiceStakingBase-flatten.sol#895) is a local variable never initialized
+ServiceStakingBase.unstake(uint256).idx (ServiceStakingBase-flatten.sol#1029) is a local variable never initialized
+ServiceStakingBase._evict(uint256[],uint256[],uint256).sCounter (ServiceStakingBase-flatten.sol#834) is a local variable never initialized
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables
-Notes: False positive.
+Notes: false positive
INFO:Detectors:
-ServiceStakingBase.calculateServiceStakingReward(uint256) (ServiceStakingToken-flatten.sol#900-927) uses a dangerous strict equality:
- - sInfo.tsStart == 0 (ServiceStakingToken-flatten.sol#906)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities
-INFO:Detectors:
-ServiceStakingBase unstake parameter from is not related to msg.sender IService(serviceRegistry).safeTransferFrom(address(this),msg.sender,serviceId) (ServiceStakingToken-flatten.sol#887)
+ServiceStakingBase unstake parameter from is not related to msg.sender IService(serviceRegistry).safeTransferFrom(address(this),msg.sender,serviceId) (ServiceStakingBase-flatten.sol#1059)
Reference: https://ventral.digital/posts/2022/8/18/sznsdaos-bountyboard-unauthorized-transferfrom-vulnerability
-Notes: False positive.
+Notes: false positive
INFO:Detectors:
-Reentrancy in ServiceStakingBase.stake(uint256) (ServiceStakingToken-flatten.sol#585-657):
+Reentrancy in ServiceStakingBase.stake(uint256) (ServiceStakingBase-flatten.sol#628-706):
External calls:
- - IService(serviceRegistry).safeTransferFrom(msg.sender,address(this),serviceId) (ServiceStakingToken-flatten.sol#654)
+ - IService(serviceRegistry).safeTransferFrom(msg.sender,address(this),serviceId) (ServiceStakingBase-flatten.sol#703)
Event emitted after the call(s):
- - ServiceStaked(serviceId,msg.sender,service.multisig,nonces) (ServiceStakingToken-flatten.sol#656)
-Reentrancy in ServiceStakingBase.unstake(uint256) (ServiceStakingToken-flatten.sol#848-895):
+ - ServiceStaked(epochCounter,serviceId,msg.sender,service.multisig,nonces) (ServiceStakingBase-flatten.sol#705)
+Reentrancy in ServiceStakingBase.unstake(uint256) (ServiceStakingBase-flatten.sol#1001-1067):
External calls:
- - IService(serviceRegistry).safeTransferFrom(address(this),msg.sender,serviceId) (ServiceStakingToken-flatten.sol#887)
+ - IService(serviceRegistry).safeTransferFrom(address(this),msg.sender,serviceId) (ServiceStakingBase-flatten.sol#1059)
Event emitted after the call(s):
- - ServiceUnstaked(serviceId,msg.sender,multisig,nonces,reward,tsStart) (ServiceStakingToken-flatten.sol#894)
+ - ServiceUnstaked(epochCounter,serviceId,msg.sender,multisig,nonces,reward) (ServiceStakingBase-flatten.sol#1066)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3
-Notes: False positive.
+Notes: false positive
INFO:Detectors:
-ServiceStakingBase._isRatioPass(uint256[],uint256[],uint256) (ServiceStakingToken-flatten.sol#676-688) uses timestamp for comparisons
- Dangerous comparisons:
- - ts > 0 && curNonces[0] > lastNonces[0] (ServiceStakingToken-flatten.sol#684)
- - ratioPass = (ratio >= livenessRatio) (ServiceStakingToken-flatten.sol#686)
-ServiceStakingBase._calculateStakingRewards() (ServiceStakingToken-flatten.sol#698-757) uses timestamp for comparisons
- Dangerous comparisons:
- - block.timestamp - tsCheckpointLast >= livenessPeriod && lastAvailableRewards > 0 (ServiceStakingToken-flatten.sol#711)
- - ts > serviceCheckpoint (ServiceStakingToken-flatten.sol#737)
-ServiceStakingBase.calculateServiceStakingReward(uint256) (ServiceStakingToken-flatten.sol#900-927) uses timestamp for comparisons
+ServiceStakingBase._isRatioPass(uint256[],uint256[],uint256) (ServiceStakingBase-flatten.sol#725-737) uses timestamp for comparisons
Dangerous comparisons:
- - sInfo.tsStart == 0 (ServiceStakingToken-flatten.sol#906)
-ServiceStakingBase.isServiceStaked(uint256) (ServiceStakingToken-flatten.sol#945-947) uses timestamp for comparisons
- Dangerous comparisons:
- - isStaked = (mapServiceInfo[serviceId].tsStart > 0) (ServiceStakingToken-flatten.sol#946)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp
-
-Notes: False positive.
-INFO:Detectors:
-SafeTransferLib.safeTransferFrom(address,address,address,uint256) (ServiceStakingToken-flatten.sol#1026-1057) uses assembly
- - INLINE ASM (ServiceStakingToken-flatten.sol#1030-1052)
-SafeTransferLib.safeTransfer(address,address,uint256) (ServiceStakingToken-flatten.sol#1068-1098) uses assembly
- - INLINE ASM (ServiceStakingToken-flatten.sol#1072-1093)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage
-
-Notes: False positive.
-INFO:Detectors:
-ServiceStakingBase.stake(uint256) (ServiceStakingToken-flatten.sol#585-657) has a high cyclomatic complexity (12).
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity
-
-Notes: False positive.
-INFO:Detectors:
-ERC721._burn(uint256) (ServiceStakingToken-flatten.sol#296-311) is never used and should be removed
-ERC721._mint(address,uint256) (ServiceStakingToken-flatten.sol#281-294) is never used and should be removed
-ERC721._safeMint(address,uint256) (ServiceStakingToken-flatten.sol#317-326) is never used and should be removed
-ERC721._safeMint(address,uint256,bytes) (ServiceStakingToken-flatten.sol#328-341) is never used and should be removed
-ServiceStakingBase._checkTokenStakingDeposit(uint256,uint256) (ServiceStakingToken-flatten.sol#571-576) is never used and should be removed
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code
-
-Notes: False positive.
-INFO:Detectors:
-Variable ERC721._ownerOf (ServiceStakingToken-flatten.sol#155) is not in mixedCase
-Variable ERC721._balanceOf (ServiceStakingToken-flatten.sol#157) is not in mixedCase
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions
-
-Notes: False positive.
-INFO:Detectors:
-SafeTransferLib.safeTransferFrom(address,address,address,uint256) (ServiceStakingToken-flatten.sol#1026-1057) uses literals with too many digits:
- - mstore(uint256,uint256)(0,0x23b872dd00000000000000000000000000000000000000000000000000000000) (ServiceStakingToken-flatten.sol#1035)
-SafeTransferLib.safeTransfer(address,address,uint256) (ServiceStakingToken-flatten.sol#1068-1098) uses literals with too many digits:
- - mstore(uint256,uint256)(0,0xa9059cbb00000000000000000000000000000000000000000000000000000000) (ServiceStakingToken-flatten.sol#1077)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits
-
-Notes: False positive.
-INFO:Detectors:
-ERC721 (ServiceStakingToken-flatten.sol#130-342) does not implement functions:
- - ERC721.tokenURI(uint256) (ServiceStakingToken-flatten.sol#149)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions
-
-Notes: False positive.
-INFO:Detectors:
-Function SafeTransferLib.safeTransferFrom(address,address,address,uint256) (ServiceStakingToken-flatten.sol#1026-1057) contains magic numbers: 4, 36, 68
-Function SafeTransferLib.safeTransfer(address,address,uint256) (ServiceStakingToken-flatten.sol#1068-1098) contains magic numbers: 4, 36
-Reference: https://github.com/pessimistic-io/slitherin/blob/master/docs/magic_number.md
-
-Notes: False positive.
-INFO:Detectors:
-ServiceStakingBase.calculateServiceStakingReward(uint256) (ServiceStakingBase-flatten.sol#899-926) uses a dangerous strict equality:
- - sInfo.tsStart == 0 (ServiceStakingBase-flatten.sol#905)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities
-
-Notes: False positive.
-INFO:Detectors:
-ServiceStakingBase unstake parameter from is not related to msg.sender IService(serviceRegistry).safeTransferFrom(address(this),msg.sender,serviceId) (ServiceStakingBase-flatten.sol#886)
-Reference: https://ventral.digital/posts/2022/8/18/sznsdaos-bountyboard-unauthorized-transferfrom-vulnerability
-
-Notes: False positive.
-INFO:Detectors:
-Reentrancy in ServiceStakingBase.stake(uint256) (ServiceStakingBase-flatten.sol#584-656):
- External calls:
- - IService(serviceRegistry).safeTransferFrom(msg.sender,address(this),serviceId) (ServiceStakingBase-flatten.sol#653)
- Event emitted after the call(s):
- - ServiceStaked(serviceId,msg.sender,service.multisig,nonces) (ServiceStakingBase-flatten.sol#655)
-Reentrancy in ServiceStakingBase.unstake(uint256) (ServiceStakingBase-flatten.sol#847-894):
- External calls:
- - IService(serviceRegistry).safeTransferFrom(address(this),msg.sender,serviceId) (ServiceStakingBase-flatten.sol#886)
- Event emitted after the call(s):
- - ServiceUnstaked(serviceId,msg.sender,multisig,nonces,reward,tsStart) (ServiceStakingBase-flatten.sol#893)
-Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3
-
-Notes: False positive.
-INFO:Detectors:
-ServiceStakingBase._isRatioPass(uint256[],uint256[],uint256) (ServiceStakingBase-flatten.sol#675-687) uses timestamp for comparisons
+ - ts > 0 && curNonces[0] > lastNonces[0] (ServiceStakingBase-flatten.sol#733)
+ - ratioPass = (ratio >= livenessRatio) (ServiceStakingBase-flatten.sol#735)
+ServiceStakingBase._calculateStakingRewards() (ServiceStakingBase-flatten.sol#748-810) uses timestamp for comparisons
Dangerous comparisons:
- - ts > 0 && curNonces[0] > lastNonces[0] (ServiceStakingBase-flatten.sol#683)
- - ratioPass = (ratio >= livenessRatio) (ServiceStakingBase-flatten.sol#685)
-ServiceStakingBase._calculateStakingRewards() (ServiceStakingBase-flatten.sol#697-756) uses timestamp for comparisons
+ - block.timestamp - tsCheckpointLast >= livenessPeriod && lastAvailableRewards > 0 (ServiceStakingBase-flatten.sol#762)
+ - ts > serviceCheckpoint (ServiceStakingBase-flatten.sol#789)
+ServiceStakingBase.checkpoint() (ServiceStakingBase-flatten.sol#880-997) uses timestamp for comparisons
Dangerous comparisons:
- - block.timestamp - tsCheckpointLast >= livenessPeriod && lastAvailableRewards > 0 (ServiceStakingBase-flatten.sol#710)
- - ts > serviceCheckpoint (ServiceStakingBase-flatten.sol#736)
-ServiceStakingBase.calculateServiceStakingReward(uint256) (ServiceStakingBase-flatten.sol#899-926) uses timestamp for comparisons
+ - serviceInactivity[i_scope_1] > maxAllowedInactivity (ServiceStakingBase-flatten.sol#969)
+ServiceStakingBase.unstake(uint256) (ServiceStakingBase-flatten.sol#1001-1067) uses timestamp for comparisons
Dangerous comparisons:
- - sInfo.tsStart == 0 (ServiceStakingBase-flatten.sol#905)
-ServiceStakingBase.isServiceStaked(uint256) (ServiceStakingBase-flatten.sol#944-946) uses timestamp for comparisons
+ - ts <= maxAllowedInactivity && availableRewards > 0 (ServiceStakingBase-flatten.sol#1014)
+ServiceStakingBase.getServiceStakingState(uint256) (ServiceStakingBase-flatten.sol#1107-1114) uses timestamp for comparisons
Dangerous comparisons:
- - isStaked = (mapServiceInfo[serviceId].tsStart > 0) (ServiceStakingBase-flatten.sol#945)
+ - sInfo.inactivity > maxAllowedInactivity (ServiceStakingBase-flatten.sol#1109)
+ - sInfo.tsStart > 0 (ServiceStakingBase-flatten.sol#1111)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp
-Notes: False positive.
+Notes: false positive
INFO:Detectors:
-ServiceStakingBase.stake(uint256) (ServiceStakingBase-flatten.sol#584-656) has a high cyclomatic complexity (12).
+ServiceStakingBase.stake(uint256) (ServiceStakingBase-flatten.sol#628-706) has a high cyclomatic complexity (13).
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity
-
-Notes: False positive.
INFO:Detectors:
-ERC721._burn(uint256) (ServiceStakingBase-flatten.sol#296-311) is never used and should be removed
-ERC721._mint(address,uint256) (ServiceStakingBase-flatten.sol#281-294) is never used and should be removed
-ERC721._safeMint(address,uint256) (ServiceStakingBase-flatten.sol#317-326) is never used and should be removed
-ERC721._safeMint(address,uint256,bytes) (ServiceStakingBase-flatten.sol#328-341) is never used and should be removed
+ERC721._burn(uint256) (ServiceStakingBase-flatten.sol#300-315) is never used and should be removed
+ERC721._mint(address,uint256) (ServiceStakingBase-flatten.sol#285-298) is never used and should be removed
+ERC721._safeMint(address,uint256) (ServiceStakingBase-flatten.sol#321-330) is never used and should be removed
+ERC721._safeMint(address,uint256,bytes) (ServiceStakingBase-flatten.sol#332-345) is never used and should be removed
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code
-Notes: False positive.
+Notes: false positive
INFO:Detectors:
-Variable ERC721._ownerOf (ServiceStakingBase-flatten.sol#155) is not in mixedCase
-Variable ERC721._balanceOf (ServiceStakingBase-flatten.sol#157) is not in mixedCase
+Variable ERC721._ownerOf (ServiceStakingBase-flatten.sol#159) is not in mixedCase
+Variable ERC721._balanceOf (ServiceStakingBase-flatten.sol#161) is not in mixedCase
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions
-Notes: False positive.
+Notes: false positive
INFO:Detectors:
-ERC721 (ServiceStakingBase-flatten.sol#130-342) does not implement functions:
- - ERC721.tokenURI(uint256) (ServiceStakingBase-flatten.sol#149)
-ServiceStakingBase (ServiceStakingBase-flatten.sol#447-954) does not implement functions:
- - ServiceStakingBase._withdraw(address,uint256) (ServiceStakingBase-flatten.sol#580)
+ERC721 (ServiceStakingBase-flatten.sol#134-346) does not implement functions:
+ - ERC721.tokenURI(uint256) (ServiceStakingBase-flatten.sol#153)
+ServiceStakingBase (ServiceStakingBase-flatten.sol#468-1141) does not implement functions:
+ - ServiceStakingBase._withdraw(address,uint256) (ServiceStakingBase-flatten.sol#622)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions
-Notes: False positive.
+Notes: false positive
INFO:Detectors:
-ServiceStakingBase.balance (ServiceStakingBase-flatten.sol#502) should be constant
+ServiceStakingBase.balance (ServiceStakingBase-flatten.sol#540) should be constant
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant
-
-Notes: False positive.
INFO:Detectors:
-In a function ServiceStakingBase.stake(uint256) (ServiceStakingBase-flatten.sol#584-656) variable ServiceStakingBase.agentIds (ServiceStakingBase-flatten.sol#508) is read multiple times
-In a function ServiceStakingBase.checkpoint() (ServiceStakingBase-flatten.sol#765-843) variable ServiceStakingBase.mapServiceInfo (ServiceStakingBase-flatten.sol#510) is read multiple times
+In a function ServiceStakingBase.stake(uint256) (ServiceStakingBase-flatten.sol#628-706) variable ServiceStakingBase.agentIds (ServiceStakingBase-flatten.sol#546) is read multiple times
+In a function ServiceStakingBase._evict(uint256[],uint256[],uint256) (ServiceStakingBase-flatten.sol#816-872) variable ServiceStakingBase.setServiceIds (ServiceStakingBase-flatten.sol#550) is read multiple times
+In a function ServiceStakingBase.checkpoint() (ServiceStakingBase-flatten.sol#880-997) variable ServiceStakingBase.mapServiceInfo (ServiceStakingBase-flatten.sol#548) is read multiple times
Reference: https://github.com/pessimistic-io/slitherin/blob/master/docs/multiple_storage_read.md
-INFO:Slither:. analyzed (25 contracts with 108 detectors), 76 result(s) found
+INFO:Slither:. analyzed (6 contracts with 108 detectors), 30 result(s) found
diff --git a/audits/internal4/analysis/slither_full_25_10_23.txt b/audits/internal4/analysis/slither_full_25_10_23.txt
new file mode 100644
index 00000000..f1900b80
--- /dev/null
+++ b/audits/internal4/analysis/slither_full_25_10_23.txt
@@ -0,0 +1,283 @@
+Notes: False positive.
+INFO:Detectors:
+ServiceStakingBase.calculateServiceStakingReward(uint256) (ServiceStakingNativeToken-flatten.sol#899-926) uses a dangerous strict equality:
+ - sInfo.tsStart == 0 (ServiceStakingNativeToken-flatten.sol#905)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities
+
+Notes: False positive.
+INFO:Detectors:
+ServiceStakingBase unstake parameter from is not related to msg.sender IService(serviceRegistry).safeTransferFrom(address(this),msg.sender,serviceId) (ServiceStakingNativeToken-flatten.sol#886)
+Reference: https://ventral.digital/posts/2022/8/18/sznsdaos-bountyboard-unauthorized-transferfrom-vulnerability
+
+Notes: False positive.
+INFO:Detectors:
+Function ServiceStakingNativeToken._withdraw(address,uint256) (ServiceStakingNativeToken-flatten.sol#975-984) contains a low level call to a custom address
+Reference: https://github.com/pessimistic-io/slitherin/blob/master/docs/call_forward_to_protected.md
+
+Notes: False positive.
+INFO:Detectors:
+Reentrancy in ServiceStakingBase.stake(uint256) (ServiceStakingNativeToken-flatten.sol#584-656):
+ External calls:
+ - IService(serviceRegistry).safeTransferFrom(msg.sender,address(this),serviceId) (ServiceStakingNativeToken-flatten.sol#653)
+ Event emitted after the call(s):
+ - ServiceStaked(serviceId,msg.sender,service.multisig,nonces) (ServiceStakingNativeToken-flatten.sol#655)
+Reentrancy in ServiceStakingBase.unstake(uint256) (ServiceStakingNativeToken-flatten.sol#847-894):
+ External calls:
+ - IService(serviceRegistry).safeTransferFrom(address(this),msg.sender,serviceId) (ServiceStakingNativeToken-flatten.sol#886)
+ Event emitted after the call(s):
+ - ServiceUnstaked(serviceId,msg.sender,multisig,nonces,reward,tsStart) (ServiceStakingNativeToken-flatten.sol#893)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3
+
+Notes: False positive.
+INFO:Detectors:
+ServiceStakingBase._isRatioPass(uint256[],uint256[],uint256) (ServiceStakingNativeToken-flatten.sol#675-687) uses timestamp for comparisons
+ Dangerous comparisons:
+ - ts > 0 && curNonces[0] > lastNonces[0] (ServiceStakingNativeToken-flatten.sol#683)
+ - ratioPass = (ratio >= livenessRatio) (ServiceStakingNativeToken-flatten.sol#685)
+ServiceStakingBase._calculateStakingRewards() (ServiceStakingNativeToken-flatten.sol#697-756) uses timestamp for comparisons
+ Dangerous comparisons:
+ - block.timestamp - tsCheckpointLast >= livenessPeriod && lastAvailableRewards > 0 (ServiceStakingNativeToken-flatten.sol#710)
+ - ts > serviceCheckpoint (ServiceStakingNativeToken-flatten.sol#736)
+ServiceStakingBase.calculateServiceStakingReward(uint256) (ServiceStakingNativeToken-flatten.sol#899-926) uses timestamp for comparisons
+ Dangerous comparisons:
+ - sInfo.tsStart == 0 (ServiceStakingNativeToken-flatten.sol#905)
+ServiceStakingBase.isServiceStaked(uint256) (ServiceStakingNativeToken-flatten.sol#944-946) uses timestamp for comparisons
+ Dangerous comparisons:
+ - isStaked = (mapServiceInfo[serviceId].tsStart > 0) (ServiceStakingNativeToken-flatten.sol#945)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp
+
+Notes: False positive.
+INFO:Detectors:
+ServiceStakingBase.stake(uint256) (ServiceStakingNativeToken-flatten.sol#584-656) has a high cyclomatic complexity (12).
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity
+
+Notes: False positive.
+INFO:Detectors:
+ERC721._burn(uint256) (ServiceStakingNativeToken-flatten.sol#296-311) is never used and should be removed
+ERC721._mint(address,uint256) (ServiceStakingNativeToken-flatten.sol#281-294) is never used and should be removed
+ERC721._safeMint(address,uint256) (ServiceStakingNativeToken-flatten.sol#317-326) is never used and should be removed
+ERC721._safeMint(address,uint256,bytes) (ServiceStakingNativeToken-flatten.sol#328-341) is never used and should be removed
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code
+
+Notes: False positive.
+INFO:Detectors:
+Low level call in ServiceStakingNativeToken._withdraw(address,uint256) (ServiceStakingNativeToken-flatten.sol#975-984):
+ - (result) = to.call{value: amount}() (ServiceStakingNativeToken-flatten.sol#980)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls
+
+Notes: False positive.
+INFO:Detectors:
+Variable ERC721._ownerOf (ServiceStakingNativeToken-flatten.sol#155) is not in mixedCase
+Variable ERC721._balanceOf (ServiceStakingNativeToken-flatten.sol#157) is not in mixedCase
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions
+
+Notes: False positive.
+INFO:Detectors:
+ERC721 (ServiceStakingNativeToken-flatten.sol#130-342) does not implement functions:
+ - ERC721.tokenURI(uint256) (ServiceStakingNativeToken-flatten.sol#149)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions
+INFO:Detectors:
+
+Notes: False positive.
+Manipulated call found: (success) = multisig.call(payload) (GnosisSafeSameAddressMultisig-flatten.sol#124) in GnosisSafeSameAddressMultisig.create(address[],uint256,bytes) (GnosisSafeSameAddressMultisig-flatten.sol#91-150)
+Both destination and calldata could be manipulated
+ The call could be fully manipulated (arbitrary call) through GnosisSafeSameAddressMultisig.create(address[],uint256,bytes) (GnosisSafeSameAddressMultisig-flatten.sol#91-150)
+Reference: https://github.com/pessimistic-io/slitherin/blob/master/docs/arbitrary_call.md
+
+Notes: False positive.
+INFO:Detectors:
+GnosisSafeSameAddressMultisig.create(address[],uint256,bytes).multisig (GnosisSafeSameAddressMultisig-flatten.sol#95) lacks a zero-check on :
+ - (success) = multisig.call(payload) (GnosisSafeSameAddressMultisig-flatten.sol#124)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation
+
+Notes: False positive.
+INFO:Detectors:
+GnosisSafeSameAddressMultisig.create(address[],uint256,bytes) (GnosisSafeSameAddressMultisig-flatten.sol#91-150) uses assembly
+ - INLINE ASM (GnosisSafeSameAddressMultisig-flatten.sol#104-106)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage
+
+Notes: False positive.
+INFO:Detectors:
+Low level call in GnosisSafeSameAddressMultisig.create(address[],uint256,bytes) (GnosisSafeSameAddressMultisig-flatten.sol#91-150):
+ - (success) = multisig.call(payload) (GnosisSafeSameAddressMultisig-flatten.sol#124)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls
+
+Notes: False positive.
+INFO:Detectors:
+In a function GnosisSafeSameAddressMultisig.create(address[],uint256,bytes) (GnosisSafeSameAddressMultisig-flatten.sol#91-150) variable GnosisSafeSameAddressMultisig.DEFAULT_DATA_LENGTH (GnosisSafeSameAddressMultisig-flatten.sol#59) is read multiple times
+Reference: https://github.com/pessimistic-io/slitherin/blob/master/docs/multiple_storage_read.md
+INFO:Detectors:
+IToken (ServiceStakingToken-flatten.sol#960-1003) has incorrect ERC721 function interface:IToken.approve(address,uint256) (ServiceStakingToken-flatten.sol#991)
+IToken (ServiceStakingToken-flatten.sol#960-1003) has incorrect ERC721 function interface:IToken.transferFrom(address,address,uint256) (ServiceStakingToken-flatten.sol#998)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface
+
+Notes: False positive.
+INFO:Detectors:
+ServiceStakingBase.calculateServiceStakingReward(uint256) (ServiceStakingToken-flatten.sol#900-927) uses a dangerous strict equality:
+ - sInfo.tsStart == 0 (ServiceStakingToken-flatten.sol#906)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities
+INFO:Detectors:
+ServiceStakingBase unstake parameter from is not related to msg.sender IService(serviceRegistry).safeTransferFrom(address(this),msg.sender,serviceId) (ServiceStakingToken-flatten.sol#887)
+Reference: https://ventral.digital/posts/2022/8/18/sznsdaos-bountyboard-unauthorized-transferfrom-vulnerability
+
+Notes: False positive.
+INFO:Detectors:
+Reentrancy in ServiceStakingBase.stake(uint256) (ServiceStakingToken-flatten.sol#585-657):
+ External calls:
+ - IService(serviceRegistry).safeTransferFrom(msg.sender,address(this),serviceId) (ServiceStakingToken-flatten.sol#654)
+ Event emitted after the call(s):
+ - ServiceStaked(serviceId,msg.sender,service.multisig,nonces) (ServiceStakingToken-flatten.sol#656)
+Reentrancy in ServiceStakingBase.unstake(uint256) (ServiceStakingToken-flatten.sol#848-895):
+ External calls:
+ - IService(serviceRegistry).safeTransferFrom(address(this),msg.sender,serviceId) (ServiceStakingToken-flatten.sol#887)
+ Event emitted after the call(s):
+ - ServiceUnstaked(serviceId,msg.sender,multisig,nonces,reward,tsStart) (ServiceStakingToken-flatten.sol#894)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3
+
+Notes: False positive.
+INFO:Detectors:
+ServiceStakingBase._isRatioPass(uint256[],uint256[],uint256) (ServiceStakingToken-flatten.sol#676-688) uses timestamp for comparisons
+ Dangerous comparisons:
+ - ts > 0 && curNonces[0] > lastNonces[0] (ServiceStakingToken-flatten.sol#684)
+ - ratioPass = (ratio >= livenessRatio) (ServiceStakingToken-flatten.sol#686)
+ServiceStakingBase._calculateStakingRewards() (ServiceStakingToken-flatten.sol#698-757) uses timestamp for comparisons
+ Dangerous comparisons:
+ - block.timestamp - tsCheckpointLast >= livenessPeriod && lastAvailableRewards > 0 (ServiceStakingToken-flatten.sol#711)
+ - ts > serviceCheckpoint (ServiceStakingToken-flatten.sol#737)
+ServiceStakingBase.calculateServiceStakingReward(uint256) (ServiceStakingToken-flatten.sol#900-927) uses timestamp for comparisons
+ Dangerous comparisons:
+ - sInfo.tsStart == 0 (ServiceStakingToken-flatten.sol#906)
+ServiceStakingBase.isServiceStaked(uint256) (ServiceStakingToken-flatten.sol#945-947) uses timestamp for comparisons
+ Dangerous comparisons:
+ - isStaked = (mapServiceInfo[serviceId].tsStart > 0) (ServiceStakingToken-flatten.sol#946)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp
+
+Notes: False positive.
+INFO:Detectors:
+SafeTransferLib.safeTransferFrom(address,address,address,uint256) (ServiceStakingToken-flatten.sol#1026-1057) uses assembly
+ - INLINE ASM (ServiceStakingToken-flatten.sol#1030-1052)
+SafeTransferLib.safeTransfer(address,address,uint256) (ServiceStakingToken-flatten.sol#1068-1098) uses assembly
+ - INLINE ASM (ServiceStakingToken-flatten.sol#1072-1093)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage
+
+Notes: False positive.
+INFO:Detectors:
+ServiceStakingBase.stake(uint256) (ServiceStakingToken-flatten.sol#585-657) has a high cyclomatic complexity (12).
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity
+
+Notes: False positive.
+INFO:Detectors:
+ERC721._burn(uint256) (ServiceStakingToken-flatten.sol#296-311) is never used and should be removed
+ERC721._mint(address,uint256) (ServiceStakingToken-flatten.sol#281-294) is never used and should be removed
+ERC721._safeMint(address,uint256) (ServiceStakingToken-flatten.sol#317-326) is never used and should be removed
+ERC721._safeMint(address,uint256,bytes) (ServiceStakingToken-flatten.sol#328-341) is never used and should be removed
+ServiceStakingBase._checkTokenStakingDeposit(uint256,uint256) (ServiceStakingToken-flatten.sol#571-576) is never used and should be removed
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code
+
+Notes: False positive.
+INFO:Detectors:
+Variable ERC721._ownerOf (ServiceStakingToken-flatten.sol#155) is not in mixedCase
+Variable ERC721._balanceOf (ServiceStakingToken-flatten.sol#157) is not in mixedCase
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions
+
+Notes: False positive.
+INFO:Detectors:
+SafeTransferLib.safeTransferFrom(address,address,address,uint256) (ServiceStakingToken-flatten.sol#1026-1057) uses literals with too many digits:
+ - mstore(uint256,uint256)(0,0x23b872dd00000000000000000000000000000000000000000000000000000000) (ServiceStakingToken-flatten.sol#1035)
+SafeTransferLib.safeTransfer(address,address,uint256) (ServiceStakingToken-flatten.sol#1068-1098) uses literals with too many digits:
+ - mstore(uint256,uint256)(0,0xa9059cbb00000000000000000000000000000000000000000000000000000000) (ServiceStakingToken-flatten.sol#1077)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits
+
+Notes: False positive.
+INFO:Detectors:
+ERC721 (ServiceStakingToken-flatten.sol#130-342) does not implement functions:
+ - ERC721.tokenURI(uint256) (ServiceStakingToken-flatten.sol#149)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions
+
+Notes: False positive.
+INFO:Detectors:
+Function SafeTransferLib.safeTransferFrom(address,address,address,uint256) (ServiceStakingToken-flatten.sol#1026-1057) contains magic numbers: 4, 36, 68
+Function SafeTransferLib.safeTransfer(address,address,uint256) (ServiceStakingToken-flatten.sol#1068-1098) contains magic numbers: 4, 36
+Reference: https://github.com/pessimistic-io/slitherin/blob/master/docs/magic_number.md
+
+Notes: False positive.
+INFO:Detectors:
+ServiceStakingBase.calculateServiceStakingReward(uint256) (ServiceStakingBase-flatten.sol#899-926) uses a dangerous strict equality:
+ - sInfo.tsStart == 0 (ServiceStakingBase-flatten.sol#905)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities
+
+Notes: False positive.
+INFO:Detectors:
+ServiceStakingBase unstake parameter from is not related to msg.sender IService(serviceRegistry).safeTransferFrom(address(this),msg.sender,serviceId) (ServiceStakingBase-flatten.sol#886)
+Reference: https://ventral.digital/posts/2022/8/18/sznsdaos-bountyboard-unauthorized-transferfrom-vulnerability
+
+Notes: False positive.
+INFO:Detectors:
+Reentrancy in ServiceStakingBase.stake(uint256) (ServiceStakingBase-flatten.sol#584-656):
+ External calls:
+ - IService(serviceRegistry).safeTransferFrom(msg.sender,address(this),serviceId) (ServiceStakingBase-flatten.sol#653)
+ Event emitted after the call(s):
+ - ServiceStaked(serviceId,msg.sender,service.multisig,nonces) (ServiceStakingBase-flatten.sol#655)
+Reentrancy in ServiceStakingBase.unstake(uint256) (ServiceStakingBase-flatten.sol#847-894):
+ External calls:
+ - IService(serviceRegistry).safeTransferFrom(address(this),msg.sender,serviceId) (ServiceStakingBase-flatten.sol#886)
+ Event emitted after the call(s):
+ - ServiceUnstaked(serviceId,msg.sender,multisig,nonces,reward,tsStart) (ServiceStakingBase-flatten.sol#893)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3
+
+Notes: False positive.
+INFO:Detectors:
+ServiceStakingBase._isRatioPass(uint256[],uint256[],uint256) (ServiceStakingBase-flatten.sol#675-687) uses timestamp for comparisons
+ Dangerous comparisons:
+ - ts > 0 && curNonces[0] > lastNonces[0] (ServiceStakingBase-flatten.sol#683)
+ - ratioPass = (ratio >= livenessRatio) (ServiceStakingBase-flatten.sol#685)
+ServiceStakingBase._calculateStakingRewards() (ServiceStakingBase-flatten.sol#697-756) uses timestamp for comparisons
+ Dangerous comparisons:
+ - block.timestamp - tsCheckpointLast >= livenessPeriod && lastAvailableRewards > 0 (ServiceStakingBase-flatten.sol#710)
+ - ts > serviceCheckpoint (ServiceStakingBase-flatten.sol#736)
+ServiceStakingBase.calculateServiceStakingReward(uint256) (ServiceStakingBase-flatten.sol#899-926) uses timestamp for comparisons
+ Dangerous comparisons:
+ - sInfo.tsStart == 0 (ServiceStakingBase-flatten.sol#905)
+ServiceStakingBase.isServiceStaked(uint256) (ServiceStakingBase-flatten.sol#944-946) uses timestamp for comparisons
+ Dangerous comparisons:
+ - isStaked = (mapServiceInfo[serviceId].tsStart > 0) (ServiceStakingBase-flatten.sol#945)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp
+
+Notes: False positive.
+INFO:Detectors:
+ServiceStakingBase.stake(uint256) (ServiceStakingBase-flatten.sol#584-656) has a high cyclomatic complexity (12).
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity
+
+Notes: False positive.
+INFO:Detectors:
+ERC721._burn(uint256) (ServiceStakingBase-flatten.sol#296-311) is never used and should be removed
+ERC721._mint(address,uint256) (ServiceStakingBase-flatten.sol#281-294) is never used and should be removed
+ERC721._safeMint(address,uint256) (ServiceStakingBase-flatten.sol#317-326) is never used and should be removed
+ERC721._safeMint(address,uint256,bytes) (ServiceStakingBase-flatten.sol#328-341) is never used and should be removed
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code
+
+Notes: False positive.
+INFO:Detectors:
+Variable ERC721._ownerOf (ServiceStakingBase-flatten.sol#155) is not in mixedCase
+Variable ERC721._balanceOf (ServiceStakingBase-flatten.sol#157) is not in mixedCase
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions
+
+Notes: False positive.
+INFO:Detectors:
+ERC721 (ServiceStakingBase-flatten.sol#130-342) does not implement functions:
+ - ERC721.tokenURI(uint256) (ServiceStakingBase-flatten.sol#149)
+ServiceStakingBase (ServiceStakingBase-flatten.sol#447-954) does not implement functions:
+ - ServiceStakingBase._withdraw(address,uint256) (ServiceStakingBase-flatten.sol#580)
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions
+
+Notes: False positive.
+INFO:Detectors:
+ServiceStakingBase.balance (ServiceStakingBase-flatten.sol#502) should be constant
+Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant
+
+Notes: False positive.
+INFO:Detectors:
+In a function ServiceStakingBase.stake(uint256) (ServiceStakingBase-flatten.sol#584-656) variable ServiceStakingBase.agentIds (ServiceStakingBase-flatten.sol#508) is read multiple times
+In a function ServiceStakingBase.checkpoint() (ServiceStakingBase-flatten.sol#765-843) variable ServiceStakingBase.mapServiceInfo (ServiceStakingBase-flatten.sol#510) is read multiple times
+Reference: https://github.com/pessimistic-io/slitherin/blob/master/docs/multiple_storage_read.md
+INFO:Slither:. analyzed (25 contracts with 108 detectors), 76 result(s) found