Skip to content

Commit

Permalink
Merge pull request #70 from valory-xyz/service_reward
Browse files Browse the repository at this point in the history
refactor: reward function for protocol-owned services
  • Loading branch information
DavidMinarsch authored Apr 20, 2022
2 parents 7043777 + d49adaa commit f42de9e
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 44 deletions.
24 changes: 23 additions & 1 deletion contracts/ServiceManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 - <[email protected]>
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
Expand All @@ -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.
Expand Down Expand Up @@ -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();
Expand Down
15 changes: 0 additions & 15 deletions contracts/ServiceRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 1 addition & 2 deletions contracts/Tokenomics.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions contracts/interfaces/ITreasury.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion deploy/contracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion scripts/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
51 changes: 50 additions & 1 deletion test/integration/ServiceRegistryManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ 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";
const configHash = {hash: "0x" + "5".repeat(64), hashFunction: "0x12", size: "0x20"};
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];
Expand All @@ -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",
Expand Down Expand Up @@ -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();
});

Expand Down Expand Up @@ -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");
});
});
});

23 changes: 0 additions & 23 deletions test/unit/registries/ServiceRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 () {
Expand Down

0 comments on commit f42de9e

Please sign in to comment.