diff --git a/contracts/staking/ServiceStakingBase.sol b/contracts/staking/ServiceStakingBase.sol index e4247cf5..6d1a7009 100644 --- a/contracts/staking/ServiceStakingBase.sol +++ b/contracts/staking/ServiceStakingBase.sol @@ -12,6 +12,11 @@ interface IMultisig { // Service Registry interface interface IService { + enum UnitType { + Component, + Agent + } + /// @dev Transfers the service that was previously approved to this contract address. /// @param from Account address to transfer from. /// @param to Account address to transfer to. @@ -36,6 +41,14 @@ interface IService { uint32 numAgentInstances, uint8 state ); + + /// @dev Gets the full set of linearized components / canonical agent Ids for a specified service. + /// @notice The service must be / have been deployed in order to get the actual data. + /// @param serviceId Service Id. + /// @return numUnitIds Number of component / agent Ids. + /// @return unitIds Set of component / agent Ids. + function getUnitIdsOfService(UnitType unitType, uint256 serviceId) external view + returns (uint256 numUnitIds, uint32[] memory unitIds); } /// @dev No rewards are available in the contract. @@ -50,6 +63,10 @@ error MaxNumServicesReached(uint256 maxNumServices); /// @param expected Expected value. error LowerThan(uint256 provided, uint256 expected); +/// @dev Required service configuration is wrong. +/// @param serviceId Service Id. +error WrongServiceConfiguration(uint256 serviceId); + /// @dev Service is not staked. /// @param serviceId Service Id. error ServiceNotStaked(uint256 serviceId); @@ -73,6 +90,27 @@ struct ServiceInfo { /// @author Andrey Lebedev - /// @author Mariapia Moscatiello - abstract contract ServiceStakingBase is IErrorsRegistries { + struct StakingParams { + // Maximum number of staking services + uint256 maxNumServices; + // Rewards per second + uint256 rewardsPerSecond; + // Minimum service staking deposit value required for staking + uint256 minStakingDeposit; + // Liveness period + uint256 livenessPeriod; + // Liveness ratio in the format of 1e18 + uint256 livenessRatio; + // Optional agent Ids requirement + uint256[] agentIds; + // Optional service multisig threshold requirement + uint256 threshold; + // Optional service number of agent instances requirement + uint256 numAgentInstances; + // Optional service configuration hash requirement + bytes32 configHash; + } + event ServiceStaked(uint256 indexed serviceId, address indexed owner, address indexed multisig, uint256 nonce); event Checkpoint(uint256 availableRewards, uint256 numServices); event ServiceUnstaked(uint256 indexed serviceId, address indexed owner, address indexed multisig, uint256 nonce, @@ -80,14 +118,24 @@ abstract contract ServiceStakingBase is IErrorsRegistries { event Deposit(address indexed sender, uint256 amount, uint256 balance, uint256 availableRewards); event Withdraw(address indexed to, uint256 amount); + // Contract version + string public constant VERSION = "0.1.0"; // Maximum number of staking services uint256 public immutable maxNumServices; // Rewards per second uint256 public immutable rewardsPerSecond; // Minimum service staking deposit value required for staking uint256 public immutable minStakingDeposit; + // Liveness period + uint256 public immutable livenessPeriod = 1 days; // Liveness ratio in the format of 1e18 uint256 public immutable livenessRatio; + // Optional service multisig threshold requirement + uint256 public immutable threshold; + // Optional service number of agent instances requirement + uint256 public immutable numAgentInstances; + // Optional service configuration hash requirement + bytes32 public immutable configHash; // ServiceRegistry contract address address public immutable serviceRegistry; @@ -97,37 +145,48 @@ abstract contract ServiceStakingBase is IErrorsRegistries { uint256 public availableRewards; // Timestamp of the last checkpoint uint256 public tsCheckpoint; + // Optional agent Ids requirement + uint256[] public agentIds; // Mapping of serviceId => staking service info mapping (uint256 => ServiceInfo) public mapServiceInfo; // Set of currently staking serviceIds uint256[] public setServiceIds; /// @dev ServiceStakingBase constructor. - /// @param _maxNumServices Maximum number of staking services. - /// @param _rewardsPerSecond Staking rewards per second (in single digits). - /// @param _minStakingDeposit Minimum staking deposit for a service to be eligible to stake. - /// @param _livenessRatio Liveness ratio: number of nonces per second (in 18 digits). + /// @param _stakingParams Service staking parameters. /// @param _serviceRegistry ServiceRegistry contract address. - constructor( - uint256 _maxNumServices, - uint256 _rewardsPerSecond, - uint256 _minStakingDeposit, - uint256 _livenessRatio, - address _serviceRegistry) - { + constructor(StakingParams memory _stakingParams, address _serviceRegistry) { // Initial checks - if (_maxNumServices == 0 || _rewardsPerSecond == 0 || _minStakingDeposit == 0 || _livenessRatio == 0) { + if (_stakingParams.maxNumServices == 0 || _stakingParams.rewardsPerSecond == 0 || + _stakingParams.minStakingDeposit == 0 || _stakingParams.livenessRatio == 0) { revert ZeroValue(); } if (_serviceRegistry == address(0)) { revert ZeroAddress(); } - maxNumServices = _maxNumServices; - rewardsPerSecond = _rewardsPerSecond; - minStakingDeposit = _minStakingDeposit; - livenessRatio = _livenessRatio; + // Assign all the required parameters + maxNumServices = _stakingParams.maxNumServices; + rewardsPerSecond = _stakingParams.rewardsPerSecond; + minStakingDeposit = _stakingParams.minStakingDeposit; + livenessRatio = _stakingParams.livenessRatio; serviceRegistry = _serviceRegistry; + + // Assign optional parameters + threshold = _stakingParams.threshold; + numAgentInstances = _stakingParams.numAgentInstances; + configHash = _stakingParams.configHash; + + // Assign agent Ids, if applicable + uint256 size = _stakingParams.agentIds.length; + if (size > 0) { + for (uint256 i = 0; i < size; ++i) { + agentIds.push(_stakingParams.agentIds[i]); + } + } + + // Set the checkpoint timestamp to be the deployment one + tsCheckpoint = block.timestamp; } /// @dev Checks token / ETH staking deposit. @@ -159,7 +218,36 @@ abstract contract ServiceStakingBase is IErrorsRegistries { } // Check the service conditions for staking - (uint96 stakingDeposit, address multisig, , , , , uint8 state) = IService(serviceRegistry).mapServices(serviceId); + (uint96 stakingDeposit, address multisig, bytes32 hash, uint256 minNumAgents, , uint256 numInstances, uint8 state) = + IService(serviceRegistry).mapServices(serviceId); + + // Check the configuration hash, if applicable + if (configHash != bytes32(0) && configHash != hash) { + revert WrongServiceConfiguration(serviceId); + } + // Check the threshold, if applicable + if (threshold > 0 && threshold != minNumAgents) { + revert WrongServiceConfiguration(serviceId); + } + // Check the number of agent instances, if applicable + if (numAgentInstances > 0 && numAgentInstances != numInstances) { + revert WrongServiceConfiguration(serviceId); + } + // Check the agent Ids requirement, if applicable + uint256 size = agentIds.length; + if (size > 0) { + (uint256 numAgents, uint32[] memory agents) = + IService(serviceRegistry).getUnitIdsOfService(IService.UnitType.Agent, serviceId); + + if (size != numAgents) { + revert WrongServiceConfiguration(serviceId); + } + for (uint256 i = 0; i < numAgents; ++i) { + if (agentIds[i] != agents[i]) { + revert WrongServiceConfiguration(serviceId); + } + } + } // The service must be deployed if (state != 4) { revert WrongServiceState(state, serviceId); @@ -191,7 +279,6 @@ abstract contract ServiceStakingBase is IErrorsRegistries { /// @param eligibleServiceIds Service Ids eligible for rewards. /// @param eligibleServiceRewards Corresponding rewards for eligible service Ids. /// @param serviceIds All the staking service Ids. - /// @param serviceNonces Current service nonces. function _calculateStakingRewards() internal view returns ( uint256 lastAvailableRewards, uint256 numServices, @@ -202,60 +289,61 @@ abstract contract ServiceStakingBase is IErrorsRegistries { uint256[] memory serviceNonces ) { - // Get available rewards and last checkpoint timestamp - lastAvailableRewards = availableRewards; - uint256 tsCheckpointLast = tsCheckpoint; - // Get the service Ids set length uint256 size = setServiceIds.length; serviceIds = new uint256[](size); - serviceNonces = new uint256[](size); - // Record service Ids and nonces + // Record service Ids for (uint256 i = 0; i < size; ++i) { // Get current service Id serviceIds[i] = setServiceIds[i]; - - // Get current service multisig nonce - address multisig = mapServiceInfo[serviceIds[i]].multisig; - serviceNonces[i] = IMultisig(multisig).nonce(); } - // If available rewards are not zero, proceed with staking calculation - if (lastAvailableRewards > 0) { - // Get necessary arrays - eligibleServiceIds = new uint256[](size); - eligibleServiceRewards = new uint256[](size); - - // Calculate each staked service reward eligibility - for (uint256 i = 0; i < size; ++i) { - // Get the service info - uint256 curServiceId = serviceIds[i]; - ServiceInfo storage curInfo = mapServiceInfo[curServiceId]; - - // Calculate the liveness nonce ratio - // Get the last service checkpoint: staking start time or the global checkpoint timestamp - uint256 serviceCheckpoint = tsCheckpointLast; - // Adjust the service checkpoint time if the service was staking less than the current staking period - if (curInfo.tsStart > tsCheckpointLast) { - serviceCheckpoint = curInfo.tsStart; - } - // Calculate the liveness ratio in 1e18 value - uint256 ratio; - // If the checkpoint was called in the exactly same block, the ratio is zero - if (block.timestamp > serviceCheckpoint) { - uint256 nonce = serviceNonces[i]; - ratio = ((nonce - curInfo.nonce) * 1e18) / (block.timestamp - serviceCheckpoint); - } + // Check the last checkpoint timestamp and the liveness period + uint256 tsCheckpointLast = tsCheckpoint; + if (block.timestamp - tsCheckpointLast >= livenessPeriod) { + // Get available rewards and last checkpoint timestamp + lastAvailableRewards = availableRewards; + + // If available rewards are not zero, proceed with staking calculation + if (lastAvailableRewards > 0) { + // Get necessary arrays + eligibleServiceIds = new uint256[](size); + eligibleServiceRewards = new uint256[](size); + serviceNonces = new uint256[](size); + + // Calculate each staked service reward eligibility + for (uint256 i = 0; i < size; ++i) { + // Get the service info + ServiceInfo storage curInfo = mapServiceInfo[serviceIds[i]]; + + // Get current service multisig nonce + serviceNonces[i] = IMultisig(curInfo.multisig).nonce(); + + // Calculate the liveness nonce ratio + // Get the last service checkpoint: staking start time or the global checkpoint timestamp + uint256 serviceCheckpoint = tsCheckpointLast; + uint256 tsStart = curInfo.tsStart; + // Adjust the service checkpoint time if the service was staking less than the current staking period + if (tsStart > serviceCheckpoint) { + serviceCheckpoint = tsStart; + } + // Calculate the liveness ratio in 1e18 value + uint256 ratio; + // If the checkpoint was called in the exactly same block, the ratio is zero + if (block.timestamp > serviceCheckpoint) { + ratio = ((serviceNonces[i] - curInfo.nonce) * 1e18) / (block.timestamp - serviceCheckpoint); + } - // Record the reward for the service if it has provided enough transactions - if (ratio >= livenessRatio) { - // Calculate the reward up until now and record its value for the corresponding service - uint256 reward = rewardsPerSecond * (block.timestamp - serviceCheckpoint); - totalRewards += reward; - eligibleServiceRewards[numServices] = reward; - eligibleServiceIds[numServices] = curServiceId; - ++numServices; + // Record the reward for the service if it has provided enough transactions + if (ratio >= livenessRatio) { + // Calculate the reward up until now and record its value for the corresponding service + uint256 reward = rewardsPerSecond * (block.timestamp - serviceCheckpoint); + totalRewards += reward; + eligibleServiceRewards[numServices] = reward; + eligibleServiceIds[numServices] = serviceIds[i]; + ++numServices; + } } } } @@ -263,35 +351,53 @@ abstract contract ServiceStakingBase is IErrorsRegistries { /// @dev Checkpoint to allocate rewards up until a current time. /// @return All staking service Ids. - /// @return Number of staking eligible services. + /// @return All staking updated nonces. + /// @return Number of reward-eligible staking services during current checkpoint period. /// @return Eligible service Ids. /// @return Eligible service rewards. - function checkpoint() public returns (uint256[] memory, uint256, uint256[] memory, uint256[] memory) { + /// @return success True, if the checkpoint was successful. + function checkpoint() public returns ( + uint256[] memory, + uint256[] memory, + uint256, + uint256[] memory, + uint256[] memory, + bool success + ) + { // Calculate staking rewards (uint256 lastAvailableRewards, uint256 numServices, uint256 totalRewards, uint256[] memory eligibleServiceIds, uint256[] memory eligibleServiceRewards, uint256[] memory serviceIds, uint256[] memory serviceNonces) = _calculateStakingRewards(); - // If available rewards are not zero, proceed with staking calculation - if (lastAvailableRewards > 0) { + // If there are eligible services, proceed with staking calculation and update rewards + if (numServices > 0) { // 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 - uint256 updatedTotalRewards = 0; - for (uint256 i = 0; i < numServices; ++i) { + uint256 updatedReward; + uint256 updatedTotalRewards; + uint256 curServiceId; + for (uint256 i = 1; i < numServices; ++i) { // Calculate the updated reward - uint256 updatedReward = (eligibleServiceRewards[i] * lastAvailableRewards) / totalRewards; + updatedReward = (eligibleServiceRewards[i] * lastAvailableRewards) / totalRewards; // Add to the total updated reward updatedTotalRewards += updatedReward; - // Add reward to the service overall reward - uint256 curServiceId = eligibleServiceIds[i]; + // Add reward to the overall service reward + curServiceId = eligibleServiceIds[i]; mapServiceInfo[curServiceId].reward += updatedReward; } - // If the reward adjustment happened to have small leftovers, add it to the last traversed service + // Process the first service in the set + updatedReward = (eligibleServiceRewards[0] * lastAvailableRewards) / totalRewards; + updatedTotalRewards += updatedReward; + curServiceId = eligibleServiceIds[0]; + // If the reward adjustment happened to have small leftovers, add it to the first service if (lastAvailableRewards > updatedTotalRewards) { - mapServiceInfo[numServices - 1].reward += lastAvailableRewards - updatedTotalRewards; + updatedReward += lastAvailableRewards - updatedTotalRewards; } + // Add reward to the overall service reward + mapServiceInfo[curServiceId].reward += updatedReward; // Set available rewards to zero lastAvailableRewards = 0; } else { @@ -311,19 +417,24 @@ abstract contract ServiceStakingBase is IErrorsRegistries { availableRewards = lastAvailableRewards; } - // Updated current service nonces - for (uint256 i = 0; i < serviceIds.length; ++i) { - // Get the current service Id - uint256 curServiceId = serviceIds[i]; - mapServiceInfo[curServiceId].nonce = serviceNonces[i]; - } + // If service nonces were updated, then the checkpoint takes place, otherwise only service Ids are returned + if (serviceNonces.length > 0) { + // Updated current service nonces + for (uint256 i = 0; i < serviceIds.length; ++i) { + // Get the current service Id + uint256 curServiceId = serviceIds[i]; + mapServiceInfo[curServiceId].nonce = serviceNonces[i]; + } - // Record the current timestamp such that next calculations start from this point of time - tsCheckpoint = block.timestamp; + // Record the current timestamp such that next calculations start from this point of time + tsCheckpoint = block.timestamp; - emit Checkpoint(lastAvailableRewards, numServices); + success = true; + + emit Checkpoint(lastAvailableRewards, numServices); + } - return (serviceIds, numServices, eligibleServiceIds, eligibleServiceRewards); + return (serviceIds, serviceNonces, numServices, eligibleServiceIds, eligibleServiceRewards, success); } /// @dev Unstakes the service. @@ -336,7 +447,7 @@ abstract contract ServiceStakingBase is IErrorsRegistries { } // Call the checkpoint - (uint256[] memory serviceIds, , , ) = checkpoint(); + (uint256[] memory serviceIds, , , , , ) = checkpoint(); // 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 @@ -380,11 +491,11 @@ abstract contract ServiceStakingBase is IErrorsRegistries { } // Calculate overall staking rewards - (uint256 lastAvailableRewards, , uint256 totalRewards, uint256[] memory eligibleServiceIds, + (uint256 lastAvailableRewards, uint256 numServices, uint256 totalRewards, uint256[] memory eligibleServiceIds, uint256[] memory eligibleServiceRewards, , ) = _calculateStakingRewards(); - // If available rewards are not zero, proceed with staking calculation - if (lastAvailableRewards > 0) { + // If there are eligible services, proceed with staking calculation and update rewards for the service Id + if (numServices > 0) { // Get the service index in the eligible service set and calculate its latest reward for (uint256 i = 0; i < eligibleServiceIds.length; ++i) { if (eligibleServiceIds[i] == serviceId) { diff --git a/contracts/staking/ServiceStaking.sol b/contracts/staking/ServiceStakingNativeToken.sol similarity index 61% rename from contracts/staking/ServiceStaking.sol rename to contracts/staking/ServiceStakingNativeToken.sol index 6c93fc23..22ed0b36 100644 --- a/contracts/staking/ServiceStaking.sol +++ b/contracts/staking/ServiceStakingNativeToken.sol @@ -3,25 +3,16 @@ pragma solidity ^0.8.21; import {ServiceStakingBase} from "./ServiceStakingBase.sol"; -/// @title ServiceStakingToken - Smart contract for staking a service by its owner when the service has an ETH as the deposit +/// @title ServiceStakingNativeToken - Smart contract for staking a service with the service having a native network token as the deposit /// @author Aleksandr Kuperman - /// @author Andrey Lebedev - /// @author Mariapia Moscatiello - -contract ServiceStaking is ServiceStakingBase { - /// @dev ServiceStaking constructor. - /// @param _maxNumServices Maximum number of staking services. - /// @param _rewardsPerSecond Staking rewards per second (in single digits). - /// @param _minStakingDeposit Minimum staking deposit for a service to be eligible to stake. - /// @param _livenessRatio Liveness ratio: number of nonces per second (in 18 digits). +contract ServiceStakingNativeToken is ServiceStakingBase { + /// @dev ServiceStakingNativeToken constructor. + /// @param _stakingParams Service staking parameters. /// @param _serviceRegistry ServiceRegistry contract address. - constructor( - uint256 _maxNumServices, - uint256 _rewardsPerSecond, - uint256 _minStakingDeposit, - uint256 _livenessRatio, - address _serviceRegistry - ) - ServiceStakingBase(_maxNumServices, _rewardsPerSecond, _minStakingDeposit, _livenessRatio, _serviceRegistry) + constructor(StakingParams memory _stakingParams, address _serviceRegistry) + ServiceStakingBase(_stakingParams, _serviceRegistry) {} /// @dev Withdraws the reward amount to a service owner. diff --git a/contracts/staking/ServiceStakingToken.sol b/contracts/staking/ServiceStakingToken.sol index 50c4a7cc..9f459b5a 100644 --- a/contracts/staking/ServiceStakingToken.sol +++ b/contracts/staking/ServiceStakingToken.sol @@ -40,23 +40,17 @@ contract ServiceStakingToken is ServiceStakingBase { address public immutable stakingToken; /// @dev ServiceStakingToken constructor. - /// @param _maxNumServices Maximum number of staking services. - /// @param _rewardsPerSecond Staking rewards per second (in single digits). - /// @param _minStakingDeposit Minimum staking deposit for a service to be eligible to stake. - /// @param _livenessRatio Liveness ratio: number of nonces per second (in 18 digits). + /// @param _stakingParams Service staking parameters. /// @param _serviceRegistry ServiceRegistry contract address. /// @param _serviceRegistryTokenUtility ServiceRegistryTokenUtility contract address. /// @param _stakingToken Address of a service staking token. constructor( - uint256 _maxNumServices, - uint256 _rewardsPerSecond, - uint256 _minStakingDeposit, - uint256 _livenessRatio, + StakingParams memory _stakingParams, address _serviceRegistry, address _serviceRegistryTokenUtility, address _stakingToken ) - ServiceStakingBase(_maxNumServices, _rewardsPerSecond, _minStakingDeposit, _livenessRatio, _serviceRegistry) + ServiceStakingBase(_stakingParams, _serviceRegistry) { // Initial checks if (_stakingToken == address(0) || _serviceRegistryTokenUtility == address(0)) { diff --git a/test/ServiceStaking.js b/test/ServiceStaking.js index 261807e5..8f929f45 100644 --- a/test/ServiceStaking.js +++ b/test/ServiceStaking.js @@ -4,7 +4,7 @@ const { ethers } = require("hardhat"); const helpers = require("@nomicfoundation/hardhat-network-helpers"); const safeContracts = require("@gnosis.pm/safe-contracts"); -describe("ServiceStaking", function () { +describe.only("ServiceStakingNativeToken", function () { let componentRegistry; let agentRegistry; let serviceRegistry; @@ -24,15 +24,18 @@ describe("ServiceStaking", function () { const maxNumServices = 10; const rewardsPerSecond = "1" + "0".repeat(15); const minStakingDeposit = 10; - const livenessRatio = "1" + "0".repeat(17); // 0.1 transaction per second (TPS) + const livenessRatio = "1" + "0".repeat(16); // 0.01 transaction per second (TPS) const AddressZero = ethers.constants.AddressZero; const defaultHash = "0x" + "5".repeat(64); + const bytes32Zero = "0x" + "0".repeat(64); const regDeposit = 1000; const regBond = 1000; const serviceId = 1; const agentIds = [1]; const agentParams = [[1, regBond]]; const threshold = 1; + const numAgentInstances = 1; + const livenessPeriod = 10; // Ten seconds const initSupply = "5" + "0".repeat(26); const payload = "0x"; @@ -78,15 +81,15 @@ describe("ServiceStaking", function () { multiSend = await MultiSend.deploy(); await multiSend.deployed(); - const ServiceStaking = await ethers.getContractFactory("ServiceStaking"); - serviceStaking = await ServiceStaking.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, - livenessRatio, serviceRegistry.address); + const ServiceStakingNativeToken = await ethers.getContractFactory("ServiceStakingNativeToken"); + serviceStaking = await ServiceStakingNativeToken.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, + livenessRatio, [], 0, 0, bytes32Zero, serviceRegistry.address); await serviceStaking.deployed(); const ServiceStakingToken = await ethers.getContractFactory("ServiceStakingToken"); serviceStakingToken = await ServiceStakingToken.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, - livenessRatio, serviceRegistry.address, serviceRegistryTokenUtility.address, token.address); - await serviceStaking.deployed(); + livenessRatio, [], 0, 0, bytes32Zero, [serviceRegistry.address, serviceRegistryTokenUtility.address], token.address); + await serviceStakingToken.deployed(); const ReentrancyAttacker = await ethers.getContractFactory("ReentrancyTokenAttacker"); reentrancyAttacker = await ReentrancyAttacker.deploy(serviceRegistryTokenUtility.address); @@ -128,26 +131,26 @@ describe("ServiceStaking", function () { context("Initialization", function () { it("Should not allow the zero values and addresses when deploying contracts", async function () { - const ServiceStaking = await ethers.getContractFactory("ServiceStaking"); + const ServiceStakingNativeToken = await ethers.getContractFactory("ServiceStakingNativeToken"); const ServiceStakingToken = await ethers.getContractFactory("ServiceStakingToken"); - await expect(ServiceStaking.deploy(0, 0, 0, 0, AddressZero)).to.be.revertedWithCustomError(ServiceStaking, "ZeroValue"); - await expect(ServiceStaking.deploy(maxNumServices, 0, 0, 0, AddressZero)).to.be.revertedWithCustomError(ServiceStaking, "ZeroValue"); - await expect(ServiceStaking.deploy(maxNumServices, rewardsPerSecond, 0, 0, AddressZero)).to.be.revertedWithCustomError(ServiceStaking, "ZeroValue"); - await expect(ServiceStaking.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, 0, AddressZero)).to.be.revertedWithCustomError(ServiceStaking, "ZeroValue"); - await expect(ServiceStaking.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, livenessRatio, AddressZero)).to.be.revertedWithCustomError(ServiceStaking, "ZeroAddress"); - - await expect(ServiceStakingToken.deploy(0, 0, 0, 0, AddressZero, AddressZero, AddressZero)).to.be.revertedWithCustomError(ServiceStakingToken, "ZeroValue"); - await expect(ServiceStakingToken.deploy(maxNumServices, 0, 0, 0, AddressZero, AddressZero, AddressZero)).to.be.revertedWithCustomError(ServiceStakingToken, "ZeroValue"); - await expect(ServiceStakingToken.deploy(maxNumServices, rewardsPerSecond, 0, 0, AddressZero, AddressZero, AddressZero)).to.be.revertedWithCustomError(ServiceStakingToken, "ZeroValue"); - await expect(ServiceStakingToken.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, 0, AddressZero, AddressZero, AddressZero)).to.be.revertedWithCustomError(ServiceStakingToken, "ZeroValue"); - await expect(ServiceStakingToken.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, livenessRatio, AddressZero, AddressZero, AddressZero)).to.be.revertedWithCustomError(ServiceStakingToken, "ZeroAddress"); - await expect(ServiceStakingToken.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, livenessRatio, serviceRegistry.address, AddressZero, AddressZero)).to.be.revertedWithCustomError(ServiceStakingToken, "ZeroAddress"); - await expect(ServiceStakingToken.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, livenessRatio, serviceRegistry.address, serviceRegistryTokenUtility.address, AddressZero)).to.be.revertedWithCustomError(ServiceStakingToken, "ZeroAddress"); + await expect(ServiceStakingNativeToken.deploy(0, 0, 0, 0, [], 0, 0, bytes32Zero, AddressZero)).to.be.revertedWithCustomError(ServiceStakingNativeToken, "ZeroValue"); + await expect(ServiceStakingNativeToken.deploy(maxNumServices, 0, 0, 0, [], 0, 0, bytes32Zero, AddressZero)).to.be.revertedWithCustomError(ServiceStakingNativeToken, "ZeroValue"); + await expect(ServiceStakingNativeToken.deploy(maxNumServices, rewardsPerSecond, 0, 0, [], 0, 0, bytes32Zero, AddressZero)).to.be.revertedWithCustomError(ServiceStakingNativeToken, "ZeroValue"); + await expect(ServiceStakingNativeToken.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, 0, [], 0, 0, bytes32Zero, AddressZero)).to.be.revertedWithCustomError(ServiceStakingNativeToken, "ZeroValue"); + await expect(ServiceStakingNativeToken.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, livenessRatio, [], 0, 0, bytes32Zero, AddressZero)).to.be.revertedWithCustomError(ServiceStakingNativeToken, "ZeroAddress"); + + await expect(ServiceStakingToken.deploy(0, 0, 0, 0, [], 0, 0, bytes32Zero, [AddressZero, AddressZero], AddressZero)).to.be.revertedWithCustomError(ServiceStakingToken, "ZeroValue"); + await expect(ServiceStakingToken.deploy(maxNumServices, 0, 0, 0, [], 0, 0, bytes32Zero, [AddressZero, AddressZero], AddressZero)).to.be.revertedWithCustomError(ServiceStakingToken, "ZeroValue"); + await expect(ServiceStakingToken.deploy(maxNumServices, rewardsPerSecond, 0, 0, [], 0, 0, bytes32Zero, [AddressZero, AddressZero], AddressZero)).to.be.revertedWithCustomError(ServiceStakingToken, "ZeroValue"); + await expect(ServiceStakingToken.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, 0, [], 0, 0, bytes32Zero, [AddressZero, AddressZero], AddressZero)).to.be.revertedWithCustomError(ServiceStakingToken, "ZeroValue"); + await expect(ServiceStakingToken.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, livenessRatio, [], 0, 0, bytes32Zero, [AddressZero, AddressZero], AddressZero)).to.be.revertedWithCustomError(ServiceStakingToken, "ZeroAddress"); + await expect(ServiceStakingToken.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, livenessRatio, [], 0, 0, bytes32Zero, [serviceRegistry.address, AddressZero], AddressZero)).to.be.revertedWithCustomError(ServiceStakingToken, "ZeroAddress"); + await expect(ServiceStakingToken.deploy(maxNumServices, rewardsPerSecond, minStakingDeposit, livenessRatio, [], 0, 0, bytes32Zero, [serviceRegistry.address, serviceRegistryTokenUtility.address], AddressZero)).to.be.revertedWithCustomError(ServiceStakingToken, "ZeroAddress"); }); }); - context("Staking to ServiceStaking and ServiceStakingToken", function () { + context("Staking to ServiceStakingNativeToken and ServiceStakingToken", function () { it("Should fail if there are no available rewards", async function () { await expect( serviceStaking.stake(serviceId) @@ -156,9 +159,9 @@ describe("ServiceStaking", function () { it("Should fail if the maximum number of staking services is reached", async function () { // Deploy a contract with max number of services equal to one - const ServiceStaking = await ethers.getContractFactory("ServiceStaking"); - const sStaking = await ServiceStaking.deploy(1, rewardsPerSecond, minStakingDeposit, livenessRatio, - serviceRegistry.address); + const ServiceStakingNativeToken = await ethers.getContractFactory("ServiceStakingNativeToken"); + const sStaking = await ServiceStakingNativeToken.deploy(1, rewardsPerSecond, minStakingDeposit, livenessRatio, + [], 0, 0, bytes32Zero, serviceRegistry.address); await sStaking.deployed(); // Deposit to the contract @@ -279,7 +282,7 @@ describe("ServiceStaking", function () { ).to.be.revertedWithCustomError(serviceStakingToken, "LowerThan"); }); - it("Stake a service at ServiceStaking and try to unstake not by the service owner", async function () { + it("Stake a service at ServiceStakingNativeToken and try to unstake not by the service owner", async function () { // Deposit to the contract await deployer.sendTransaction({to: serviceStaking.address, value: ethers.utils.parseEther("1")}); @@ -348,7 +351,7 @@ describe("ServiceStaking", function () { const multisig = await ethers.getContractAt("GnosisSafe", service.multisig); // Increase the time while the service does not reach the required amount of transactions per second (TPS) - await helpers.time.increase(1000); + await helpers.time.increase(livenessPeriod); // Calculate service staking reward that must be zero const reward = await serviceStaking.calculateServiceStakingReward(serviceId); @@ -366,7 +369,7 @@ describe("ServiceStaking", function () { snapshot.restore(); }); - it("Stake and unstake with the service activity", async function () { + it.only("Stake and unstake with the service activity", async function () { // Take a snapshot of the current state of the blockchain const snapshot = await helpers.takeSnapshot(); @@ -389,6 +392,9 @@ describe("ServiceStaking", function () { let signMessageData = await safeContracts.safeSignMessage(agentInstances[0], multisig, txHashData, 0); await safeContracts.executeTx(multisig, txHashData, [signMessageData], 0); + // Increase the time for the liveness period + await helpers.time.increase(livenessPeriod); + // Call the checkpoint at this time await serviceStaking.checkpoint(); @@ -398,6 +404,9 @@ describe("ServiceStaking", function () { signMessageData = await safeContracts.safeSignMessage(agentInstances[0], multisig, txHashData, 0); await safeContracts.executeTx(multisig, txHashData, [signMessageData], 0); + // Increase the time for the liveness period + await helpers.time.increase(livenessPeriod); + // Calculate service staking reward that must be greater than zero const reward = await serviceStaking.calculateServiceStakingReward(serviceId); expect(reward).to.greaterThan(0);