diff --git a/contracts/ServiceManager.sol b/contracts/ServiceManager.sol index a7bdb4e3..22d80e72 100644 --- a/contracts/ServiceManager.sol +++ b/contracts/ServiceManager.sol @@ -6,16 +6,23 @@ import "@openzeppelin/contracts/security/Pausable.sol"; import "./interfaces/IErrors.sol"; import "./interfaces/IStructs.sol"; import "./interfaces/IService.sol"; +import "./interfaces/ITreasury.sol"; /// @title Service Manager - Periphery smart contract for managing services /// @author Aleksandr Kuperman - contract ServiceManager is IErrors, IStructs, Ownable, Pausable { + event TreasuryUpdated(address treasury); event MultisigCreate(address multisig); + event RewardService(uint256 serviceId, uint256 amount); + // Service registry address address public immutable serviceRegistry; + // Treasury address + address public treasury; - constructor(address _serviceRegistry) { + constructor(address _serviceRegistry, address _treasury) { serviceRegistry = _serviceRegistry; + treasury = _treasury; } /// @dev Fallback function @@ -28,6 +35,13 @@ contract ServiceManager is IErrors, IStructs, Ownable, Pausable { revert WrongFunction(); } + /// @dev Changes the treasury address. + /// @param newTreasury Address of a new treasury. + function changeTreasury(address newTreasury) external onlyOwner { + treasury = newTreasury; + emit TreasuryUpdated(newTreasury); + } + /// @dev Creates a new service. /// @param owner Individual that creates and controls a service. /// @param name Name of the service. @@ -130,6 +144,14 @@ contract ServiceManager is IErrors, IStructs, Ownable, Pausable { success = IService(serviceRegistry).destroy(msg.sender, serviceId); } + /// @dev Rewards the protocol-owned service with an ETH payment. + /// @param serviceId Service Id. + function serviceReward(uint256 serviceId) external payable + { + ITreasury(treasury).depositETHFromService{value: msg.value}(serviceId); + emit RewardService(serviceId, msg.value); + } + /// @dev Pauses the contract. function pause() external onlyOwner { _pause(); diff --git a/contracts/ServiceRegistry.sol b/contracts/ServiceRegistry.sol index a16eab87..08c294ac 100644 --- a/contracts/ServiceRegistry.sol +++ b/contracts/ServiceRegistry.sol @@ -23,7 +23,6 @@ contract ServiceRegistry is IErrors, IStructs, Ownable, ERC721Enumerable, Reentr event TerminateService(address owner, uint256 serviceId); event OperatorSlashed(uint256 amount, address operator, uint256 serviceId); event OperatorUnbond(address operator, uint256 serviceId); - event RewardService(uint256 serviceId, uint256 amount); event DeployService(address owner, uint256 serviceId); enum ServiceState { @@ -47,8 +46,6 @@ contract ServiceRegistry is IErrors, IStructs, Ownable, ERC721Enumerable, Reentr struct Service { // Registration activation deposit uint256 securityDeposit; - // Reward balance - uint256 rewardBalance; address proxyContract; // Multisig address for agent instances address multisig; @@ -539,18 +536,6 @@ contract ServiceRegistry is IErrors, IStructs, Ownable, ERC721Enumerable, Reentr success = true; } - /// @dev Rewards the service with payment. - /// @param serviceId Service Id. - /// @return rewardBalance Actual reward balance of a service Id. - function reward(uint256 serviceId) public serviceExists(serviceId) nonReentrant payable - returns (uint256 rewardBalance) - { - rewardBalance = _mapServices[serviceId].rewardBalance; - rewardBalance += msg.value; - _mapServices[serviceId].rewardBalance = rewardBalance; - emit RewardService(serviceId, msg.value); - } - /// @dev Terminates the service. /// @param owner Owner of the service. /// @param serviceId Service Id to be updated. diff --git a/contracts/Tokenomics.sol b/contracts/Tokenomics.sol index d5c26639..be9f1801 100644 --- a/contracts/Tokenomics.sol +++ b/contracts/Tokenomics.sol @@ -209,8 +209,7 @@ contract Tokenomics is IErrors, IStructs, Ownable { } /// @dev Tracks the deposit token amount during the epoch. - function trackServicesETHRevenue(uint256[] memory serviceIds, uint256[] memory amounts) - public onlyTreasury { + function trackServicesETHRevenue(uint256[] memory serviceIds, uint256[] memory amounts) public onlyTreasury { // Loop over service Ids and track their amounts uint256 numServices = serviceIds.length; for (uint256 i = 0; i < numServices; ++i) { diff --git a/contracts/interfaces/ITreasury.sol b/contracts/interfaces/ITreasury.sol index 85be3fb1..b7b753de 100644 --- a/contracts/interfaces/ITreasury.sol +++ b/contracts/interfaces/ITreasury.sol @@ -9,6 +9,10 @@ interface ITreasury { /// @param olaMintAmount Amount of OLA token issued. function depositTokenForOLA(uint256 tokenAmount, address token, uint256 olaMintAmount) external; + /// @dev Deposits ETH from protocol-owned service. + /// @param serviceId Service Id. + function depositETHFromService(uint256 serviceId) external payable; + /// @dev Allows manager to withdraw specified tokens from reserves /// @param tokenAmount Token amount to get reserves from. /// @param token Token address. diff --git a/deploy/contracts.js b/deploy/contracts.js index e3c2aa97..dcd73f39 100644 --- a/deploy/contracts.js +++ b/deploy/contracts.js @@ -91,7 +91,8 @@ module.exports = async () => { await serviceRegistry.deployed(); const ServiceManager = await ethers.getContractFactory("ServiceManager"); - const serviceManager = await ServiceManager.deploy(serviceRegistry.address); + // Treasury address is irrelevant at the moment + const serviceManager = await ServiceManager.deploy(serviceRegistry.address, deployer.address); await serviceManager.deployed(); console.log("ServiceRegistry deployed to:", serviceRegistry.address); diff --git a/scripts/deploy.js b/scripts/deploy.js index da9f1026..122e0394 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -91,7 +91,8 @@ async function main() { await serviceRegistry.deployed(); const ServiceManager = await ethers.getContractFactory("ServiceManager"); - const serviceManager = await ServiceManager.deploy(serviceRegistry.address); + // Treasury address is irrelevant at the moment + const serviceManager = await ServiceManager.deploy(serviceRegistry.address, deployer.address); await serviceManager.deployed(); console.log("ServiceRegistry deployed to:", serviceRegistry.address); diff --git a/test/integration/ServiceRegistryManager.js b/test/integration/ServiceRegistryManager.js index 6d3f9380..58d28d16 100644 --- a/test/integration/ServiceRegistryManager.js +++ b/test/integration/ServiceRegistryManager.js @@ -11,6 +11,9 @@ describe("ServiceRegistry integration", function () { let serviceRegistry; let serviceManager; let gnosisSafeMultisig; + let token; + let treasury; + let tokenomics; let signers; const name = "service name"; const description = "service description"; @@ -18,6 +21,7 @@ describe("ServiceRegistry integration", function () { const regBond = 1000; const regDeposit = 1000; const regFine = 500; + const regReward = 2000; const agentIds = [1, 2]; const agentParams = [[3, regBond], [4, regBond]]; const serviceIds = [1, 2]; @@ -27,6 +31,7 @@ describe("ServiceRegistry integration", function () { const componentHash1 = {hash: "0x" + "1".repeat(64), hashFunction: "0x12", size: "0x20"}; const componentHash2 = {hash: "0x" + "2".repeat(64), hashFunction: "0x12", size: "0x20"}; const payload = "0x"; + const AddressZero = "0x" + "0".repeat(40); beforeEach(async function () { const ComponentRegistry = await ethers.getContractFactory("ComponentRegistry"); componentRegistry = await ComponentRegistry.deploy("agent components", "MECHCOMP", @@ -54,10 +59,27 @@ describe("ServiceRegistry integration", function () { gnosisSafeMultisig = await GnosisSafeMultisig.deploy(gnosisSafeL2.address, gnosisSafeProxyFactory.address); await gnosisSafeMultisig.deployed(); + const Token = await ethers.getContractFactory("OLA"); + token = await Token.deploy(); + await token.deployed(); + + // Depositary and dispenser are irrelevant in this set of tests, tokenomics will be correctly assigned below + const Treasury = await ethers.getContractFactory("Treasury"); + treasury = await Treasury.deploy(token.address, AddressZero, AddressZero, AddressZero); + await treasury.deployed(); + const ServiceManager = await ethers.getContractFactory("ServiceManager"); - serviceManager = await ServiceManager.deploy(serviceRegistry.address); + serviceManager = await ServiceManager.deploy(serviceRegistry.address, treasury.address); await serviceManager.deployed(); + const Tokenomics = await ethers.getContractFactory("Tokenomics"); + tokenomics = await Tokenomics.deploy(token.address, treasury.address, AddressZero, 1, componentRegistry.address, + agentRegistry.address, serviceRegistry.address); + await tokenomics.deployed(); + + // Change to the correct tokenomics address + await treasury.changeTokenomics(tokenomics.address); + signers = await ethers.getSigners(); }); @@ -515,6 +537,33 @@ describe("ServiceRegistry integration", function () { const newContractBalance = Number(await ethers.provider.getBalance(serviceRegistry.address)); expect(newContractBalance).to.equal(contractBalance - regFine - regDeposit); }); + + it("Reward a protocol-owned service", async function () { + const somebody = signers[1]; + const manager = signers[2]; + const owner = signers[3]; + await agentRegistry.changeManager(manager.address); + + // Create an agent and a service + await agentRegistry.connect(manager).create(owner.address, owner.address, componentHash, + description, []); + await serviceRegistry.changeManager(serviceManager.address); + await serviceManager.serviceCreate(owner.address, name, description, configHash, [1], [[1, regBond]], 1); + + // Should fail if nothing is sent + await expect( + serviceManager.connect(somebody).serviceReward(serviceIds[0]) + ).to.be.revertedWith("ZeroValue"); + + // Should fail on a non-existent service + await expect( + serviceManager.connect(somebody).serviceReward(serviceIds[1], {value: regReward}) + ).to.be.revertedWith("ServiceDoesNotExist"); + + const reward = await serviceManager.connect(somebody).serviceReward(serviceIds[0], {value: regReward}); + const result = await reward.wait(); + expect(result.events[1].event).to.equal("RewardService"); + }); }); }); diff --git a/test/unit/registries/ServiceRegistry.js b/test/unit/registries/ServiceRegistry.js index 84930bb0..cde70c2a 100644 --- a/test/unit/registries/ServiceRegistry.js +++ b/test/unit/registries/ServiceRegistry.js @@ -16,7 +16,6 @@ describe("ServiceRegistry", function () { const regBond = 1000; const regDeposit = 1000; const regFine = 500; - const regReward = 2000; const agentIds = [1, 2]; const agentParams = [[3, regBond], [4, regBond]]; const serviceId = 1; @@ -1240,28 +1239,6 @@ describe("ServiceRegistry", function () { const unbond = await serviceRegistry.connect(serviceManager).callStatic.unbond(operator, serviceId); expect(Number(unbond.refund)).to.equal(0); }); - - it("Reward a service twice, get its reward balance", async function () { - const mechManager = signers[3]; - const serviceManager = signers[4]; - const owner = signers[5].address; - const somebody = signers[6]; - const maxThreshold = 2; - - // Create an agent - await agentRegistry.changeManager(mechManager.address); - await agentRegistry.connect(mechManager).create(owner, owner, agentHash, description, []); - - // Create a service and activate the agent instance registration - await serviceRegistry.changeManager(serviceManager.address); - await serviceRegistry.connect(serviceManager).createService(owner, name, description, configHash, [1], - [[2, regBond]], maxThreshold); - - // Reward service twice and check the result - let reward = await serviceRegistry.connect(somebody).reward(serviceId, {value: regReward}); - reward = await serviceRegistry.connect(somebody).callStatic.reward(serviceId, {value: regReward}); - expect(reward).to.equal(2 * regReward); - }); }); context("Destroying the service", async function () {