Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

24/Service registry #28

Draft
wants to merge 23 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 76 additions & 26 deletions VDA-Service-Registry/contracts/ServiceRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ contract ServiceRegistry is Ownable {
mapping (bytes32 => ServiceInfo) serviceInfos;
// did => AccountInfo
mapping (address => AccountInfo) accountInfos;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to use AccountInfo struct.

EnumerableSet.Bytes32Set serviceIdAry;

// EnumerableSet.Bytes32Set pendingRemovalIds;


/// @dev Registered service info
struct ServiceDetail {
address identity;
string infraType;
string serviceType;
string endpointUri;
string country;
Expand All @@ -57,7 +57,6 @@ contract ServiceRegistry is Ownable {
uint256 creditAmount;
uint256 expireTime;
uint256 lastClaimTime;
// uint256 connectCounts;
mapping (address => uint256) expireTimes;
mapping (address => uint256) pricePerDays;
EnumerableSet.AddressSet connectedAccounts;
Expand All @@ -77,8 +76,6 @@ contract ServiceRegistry is Ownable {
uint256 pricePerDayPerAccount;
}



modifier onlyVerifiedSignature(address identity, bytes calldata signature) {
// require signature is signed by identity
bytes memory rightSign = hex"67de2d20880a7d27b71cdcb38817ba95800ca82dff557cedd91b96aacb9062e80b9e0b8cb9614fd61ce364502349e9079c26abaa21890d7bc2f1f6c8ff77f6261c";
Expand Down Expand Up @@ -132,19 +129,30 @@ contract ServiceRegistry is Ownable {
// string calldata proof,
bytes calldata signature) public onlyVerifiedSignature(identity, signature)
{
// Generate new serviceId from {DID} and {serviceType}
bytes32 serviceId = keccak256(abi.encodePacked(identity, inputParam.serviceType));
// Check if service has already registered
bool isRegistered = registeredIds[identity].contains(serviceId);
require(!isRegistered, "Service has already registered!");
registeredIds[identity].add(serviceId);
serviceIdAry.add(serviceId);
// Get account info by {DID}
AccountInfo storage _account = accountInfos[identity];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First check the account exists?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

// Get infraType; ex, typeLookup["VeridaDatabase"] = "database"
string memory infraType = typeLookup[inputParam.serviceType];
// Verida Infrastructure Operator will need registerPrice tokens to register service.
// ex, vdaPerAccount["database"] = 300;
// maxAccounts * vdaPerAccount["database"];
uint256 registerPrice = inputParam.maxAccounts * vdaPerAccount[infraType];
// Check the Operator's credit amount
require(_account.creditAmount >= registerPrice, "Not enough credit to register service");
// (bool success,) = payable(address(this)).call{value: registerPrice}("");
// require(success, "Sending VDA to contract failed");


_account.creditAmount = _account.creditAmount - registerPrice;
// Register new service
ServiceInfo storage _service = serviceInfos[serviceId];
_service.serviceDetail.identity = identity;
_service.serviceDetail.infraType = infraType;
_service.serviceDetail.serviceType = inputParam.serviceType;
_service.serviceDetail.endpointUri = inputParam.endpointUri;
_service.serviceDetail.country = inputParam.country;
Expand All @@ -166,26 +174,28 @@ contract ServiceRegistry is Ownable {
uint256 pricePerAccount,
bytes calldata signature
) public payable onlyVerifiedSignature(identity, signature) {
// Get service from {serviceId}
ServiceInfo storage _service = serviceInfos[serviceId];
// Check if service updating time limit
require(block.timestamp > _service.expireTime, "Cannot update service due to limit to priceChangeDelayDays");
uint256 connectedCount = getConnectedAccountCount(serviceId);
// Cannot set maxAccounts less than number of connected accounts
require(maxAccounts > connectedCount, "Value can't be lower than the current number of connected accounts");
string memory infraType = typeLookup[_service.serviceDetail.serviceType];
AccountInfo storage _account = accountInfos[identity];
if(_service.serviceDetail.maxAccounts > maxAccounts) {
// Decrease maxAccounts, so Operator will receive the rest tokens
uint256 price = (_service.serviceDetail.maxAccounts - maxAccounts) * vdaPerAccount[infraType];
_service.creditAmount = _service.creditAmount - price;
// (bool success, ) = msg.sender.call{value: price}("");
// require(success, "Cannot update service");
_account.creditAmount = _account.creditAmount + price;
} else if(_service.serviceDetail.maxAccounts < maxAccounts) {
// Increase maxAccounts, so Operator will pay more tokens
uint256 price = (maxAccounts - _service.serviceDetail.maxAccounts) * vdaPerAccount[infraType];
_service.creditAmount = _service.creditAmount + price;
// require(msg.value > price, "Cannot update service");
_account.creditAmount = _account.creditAmount - price;
}
// Update the service status
_service.serviceDetail.maxAccounts = maxAccounts;
// _service.lastPricePerDayPerAccount = _service.pricePerDayPerAccount;
_service.serviceDetail.pricePerDayPerAccount = pricePerAccount;
_service.expireTime = block.timestamp + priceChangeDelayDays * 1 days;
_service.status = ServiceStatus.PendingUpdate;
Expand All @@ -199,10 +209,10 @@ contract ServiceRegistry is Ownable {
bytes32 serviceId,
bytes calldata signature
) public onlyVerifiedSignature(identity, signature) {
// bool isPending = pendingRemovalIds.contains(serviceId);
bool isPending = serviceInfos[serviceId].status == ServiceStatus.PendingRemoval;
// Check if service is already pending removal
require(!isPending, "Service is pending removal");
// pendingRemovalIds.add(serviceId);
// Update service status only
serviceInfos[serviceId].status = ServiceStatus.PendingRemoval;
serviceInfos[serviceId].expireTime = block.timestamp + deregisterDelayDays * 1 days;

Expand All @@ -214,18 +224,22 @@ contract ServiceRegistry is Ownable {
bytes32 serviceId,
bytes calldata signature
) external onlyVerifiedSignature(identity, signature) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to check service status.

// Remove service from the storage according the status
ServiceInfo storage _service = serviceInfos[serviceId];
require((serviceInfos[serviceId].status == ServiceStatus.PendingRemoval) && (block.timestamp > serviceInfos[serviceId].expireTime), "Not ready to remove");
AccountInfo storage _account = accountInfos[identity];
_account.creditAmount = _account.creditAmount + _service.creditAmount;
delete serviceInfos[serviceId];
registeredIds[identity].remove(serviceId);
serviceIdAry.remove(serviceId);
}

function addCredit(
function _addCredit(
address identity,
uint256 numCredit,
address sender
) internal {
// Account will lockup tokens in the contract and increase his creditAmount
uint256 balanceBefore = vdaToken.balanceOf(sender);
require(balanceBefore >= numCredit, "Insufficient funds to add credit");
vdaToken.transferFrom(sender, address(this), numCredit);
Expand All @@ -242,23 +256,22 @@ contract ServiceRegistry is Ownable {
uint256 numCredit,
bytes calldata signature
) public onlyVerifiedSignature(identity, signature) {
addCredit(identity, numCredit, msg.sender);
_addCredit(identity, numCredit, msg.sender);
}

function removeCredit(
function _removeCredit(
address identity,
uint256 numCredit,
address sender
) internal {
// Account will free up tokens from the contract and decrease the creditAmount
AccountInfo storage _account = accountInfos[identity];

require(numCredit > 0, "Value cannot be zero");
require(_account.creditAmount >= numCredit, "Not enough credit to remove");
uint256 balanceBefore = vdaToken.balanceOf(address(this));
require(balanceBefore >= numCredit, "Not enough token in the contract");
vdaToken.transfer(sender, numCredit);
// (bool success, ) = msg.sender.call{value: numCredit}("");
// require(success, "Sending VDA failed");
_account.creditAmount = _account.creditAmount - numCredit;

emit RemoveCredit(identity, numCredit);
Expand All @@ -270,7 +283,7 @@ contract ServiceRegistry is Ownable {
uint256 numCredit,
bytes calldata signature
) public onlyVerifiedSignature(identity, signature) {
removeCredit(identity, numCredit, msg.sender);
_removeCredit(identity, numCredit, msg.sender);
}

/// @dev Account{identity} connect to the Service{serviceId}
Expand All @@ -280,18 +293,21 @@ contract ServiceRegistry is Ownable {
bytes calldata signature
) public payable onlyVerifiedSignature(identity, signature) {
ServiceInfo storage _service = serviceInfos[serviceId];
// bool isPendingRemoval = pendingRemovalIds.contains(serviceId);
bool isPendingRemoval = _service.status == ServiceStatus.PendingRemoval;
// Check the service status
require(!isPendingRemoval, "Service is pending removal");
require(!_service.connectedAccounts.contains(identity), "Already connected");
require(_service.serviceDetail.maxAccounts > _service.connectedAccounts.length(), "Service hits maximum number of connected accounts");
uint256 price = _service.serviceDetail.pricePerDayPerAccount * minimumDaysCreditPerService;
AccountInfo storage _account = accountInfos[identity];
require(_account.creditAmount >= price, "Not enough VDA to connect service");
// Account have to pay specific tokens to connect to the service
// amount = pricePerDayPerAccount * minimumDaysCreditPerService
// prciePerDayPerAccount will be set by Operator when he register the service
// minimumDaysCreditPerService(30 days) is the config variable.
_account.creditAmount = _account.creditAmount - price;
_service.expireTimes[identity] = block.timestamp + minimumDaysCreditPerService * 1 days;
_service.pricePerDays[identity] = _service.serviceDetail.pricePerDayPerAccount;
// _service.connectCounts = _service.connectCounts + 1;
_service.creditAmount = _service.creditAmount + price;
_service.connectedAccounts.add(identity);

Expand All @@ -304,31 +320,62 @@ contract ServiceRegistry is Ownable {
bytes32 serviceId,
bytes calldata signature
) public onlyVerifiedSignature(identity, signature) {
// bool isConnected = connectInfos[serviceId].contains(identity);
// Check if account is connected to the service
bool isConnected = serviceInfos[serviceId].connectedAccounts.contains(identity);
require(isConnected, "Account is not connected to service");
ServiceInfo storage _service = serviceInfos[serviceId];
// removeCredit(identity, _service.pricePerDayPerAccount * minimumDaysCreditPerService, signature);
// Account will receive the remained tokens
uint256 price = _service.pricePerDays[identity] * calcDays(block.timestamp, serviceInfos[serviceId].expireTimes[identity]);
// uint256 price = _service.serviceDetail.pricePerDayPerAccount * (minimumDaysCreditPerService - calcDays(serviceInfos[serviceId].connectTimes[identity], block.timestamp));
accountInfos[identity].creditAmount = accountInfos[identity].creditAmount + price;
_service.creditAmount = _service.creditAmount - price;
// connectInfos[serviceId].remove(identity);
delete _service.expireTimes[identity];
delete _service.pricePerDays[identity];
// _service.connectCounts = _service.connectCounts - 1;
_service.connectedAccounts.remove(identity);
}

/// @dev Discover the available services so accounts can select services
function discoverService(
string memory infraType,
string memory serviceType,
string memory country,
uint256 maxPricePerDay
) external view returns(bytes32[] memory) {
bytes32[] memory serviceIds = new bytes32[](serviceIdAry.length());
uint cnt = 0;
for(uint i = 0;i<serviceIdAry.length();i++) {
bytes32 _id = serviceIdAry.at(i);
ServiceDetail memory _detail = serviceInfos[_id].serviceDetail;
if((compareStrings(_detail.infraType, infraType) || compareStrings(infraType, "")) &&
(compareStrings(_detail.serviceType, serviceType) || compareStrings(serviceType, "")) &&
(compareStrings(_detail.country, country) || compareStrings(country, "")) &&
_detail.pricePerDayPerAccount <= maxPricePerDay
) {
serviceIds[cnt] = _id;
cnt = cnt + 1;
}
}
bytes32[] memory res = new bytes32[](cnt);
for(uint i = 0;i<cnt;i++)
res[i] = serviceIds[i];
return res;
}

function compareStrings(string memory a, string memory b) internal pure returns(bool) {
return (keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)));
}

/// @dev Verida Infrastructure Operator will call this function every week.
function claim(
address identity,
bytes32 serviceId,
bytes calldata signature
) public onlyVerifiedSignature(identity, signature) {
ServiceInfo storage _service = serviceInfos[serviceId];
// Check the 7 days limit to claim
require(block.timestamp >= _service.lastClaimTime + 7 days, "Should claim after 7 days from the last claim");
uint256 amount = 0;
uint256 currentTime = block.timestamp;
// Calculate the claimable tokens from connected accounts
for(uint i = 0;i<_service.connectedAccounts.length();i++){
address _did = _service.connectedAccounts.at(i);
uint256 conTime = _service.expireTimes[_did] - minimumDaysCreditPerService * 1 days;
Expand All @@ -342,6 +389,7 @@ contract ServiceRegistry is Ownable {
}
require(_service.creditAmount >= amount, "Cannot claim, not enough credit");
_service.creditAmount = _service.creditAmount - amount;
// If Operator tries to claim after 14 days, the tokens will be stored in the contract and he cannot claim that tokens.
if(currentTime <= _service.lastClaimTime + 14 days) {
accountInfos[identity].creditAmount = accountInfos[identity].creditAmount + amount;
} else {
Expand Down Expand Up @@ -378,6 +426,7 @@ contract ServiceRegistry is Ownable {
/// @dev Get detail of the Service{serviceId}
function getServiceDetail(bytes32 serviceId) public view returns(
address identity,
string memory infraType,
string memory serviceType,
string memory endpointUri,
string memory country,
Expand All @@ -386,6 +435,7 @@ contract ServiceRegistry is Ownable {
uint256 startTime
) {
identity = serviceInfos[serviceId].serviceDetail.identity;
infraType = serviceInfos[serviceId].serviceDetail.infraType;
serviceType = serviceInfos[serviceId].serviceDetail.serviceType;
endpointUri = serviceInfos[serviceId].serviceDetail.endpointUri;
country = serviceInfos[serviceId].serviceDetail.country;
Expand Down
19 changes: 19 additions & 0 deletions VDA-Service-Registry/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,25 @@ describe("Service Registry", function () {
})
});

describe("Discover Service", async () => {
it("Discover services", async () => {
const res = await registryContract.discoverService("database", "VeridaDatabase", "India", 1000 );
for(let i = 0;i<res.length;i++) {
const _detail = await registryContract.getServiceDetail(res[i]);
expect(_detail.infraType).to.be.eq("database");
expect(_detail.serviceType).to.be.eq("VeridaDatabase");
expect(_detail.country).to.be.eq("India");
}
const res1 = await registryContract.discoverService("database", "VeridaDatabase", "", 1000 );
for(let i = 0;i<res1.length;i++) {
const _detail = await registryContract.getServiceDetail(res1[i]);
console.log("detail:", _detail);
expect(_detail.infraType).to.be.eq("database");
expect(_detail.serviceType).to.be.eq("VeridaDatabase");
}
})
})

describe("Connect Service", async () => {
it("Successfully VDA accounts connect to the service", async () => {
// [0] = VeridaDatabase : max = 2, pricePerDay = 400
Expand Down