From df56556e87d3e93acca78636b173eaf30b5a7ec3 Mon Sep 17 00:00:00 2001 From: Sebastian Gerske Date: Mon, 29 Oct 2018 17:28:40 +0100 Subject: [PATCH] added serviceAgreementTemplates, wip --- package-lock.json | 8 +- package.json | 2 +- src/keeper/ContractHandler.ts | 14 +++- src/keeper/contracts/ContractBase.ts | 23 +++++- src/keeper/contracts/ServiceAgreement.ts | 23 ++++++ .../contracts/conditions/AccessConditions.ts | 10 +++ .../contracts/conditions/PaymentConditions.ts | 10 +++ src/ocean/ServiceAgreement.ts | 73 +++++++++++++++++++ src/ocean/ServiceAgreementTemplate.ts | 67 +++++++++++++++++ test/keeper/ContractBase.test.ts | 31 +++++++- test/mocks/ContractBase.Mock.ts | 2 +- test/ocean/ServiceAgreement.test.ts | 56 ++++++++++++++ test/ocean/ServiceAgreementTemplate.test.ts | 34 +++++++++ 13 files changed, 341 insertions(+), 12 deletions(-) create mode 100644 src/keeper/contracts/conditions/AccessConditions.ts create mode 100644 src/keeper/contracts/conditions/PaymentConditions.ts create mode 100644 src/ocean/ServiceAgreement.ts create mode 100644 src/ocean/ServiceAgreementTemplate.ts create mode 100644 test/ocean/ServiceAgreement.test.ts create mode 100644 test/ocean/ServiceAgreementTemplate.test.ts diff --git a/package-lock.json b/package-lock.json index 45e0a19..c26eb70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -120,9 +120,9 @@ } }, "@oceanprotocol/keeper-contracts": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@oceanprotocol/keeper-contracts/-/keeper-contracts-0.3.3.tgz", - "integrity": "sha512-UVt9m9WmFm4mTDWmMekT8lhBh2hWr2baEVnF0dEN3OL5BDAWoynFQeeKtUqTepVuOVgNy5bHYKLiHkCWzbOEqA==" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@oceanprotocol/keeper-contracts/-/keeper-contracts-0.4.1.tgz", + "integrity": "sha512-UmgPsJul9ZJb5BPIa1qfMpokJWzdROni/wisd4Dnm+wwHUA7/ymsr34zVn+4v+uAcMLHAYPKbsROBT5xlbiKAQ==" }, "@oceanprotocol/secret-store-client": { "version": "0.0.6", @@ -1802,7 +1802,7 @@ }, "es6-promisify": { "version": "5.0.0", - "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "requires": { "es6-promise": "^4.0.3" diff --git a/package.json b/package.json index 905d722..8e19883 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "node": ">=8 <10" }, "dependencies": { - "@oceanprotocol/keeper-contracts": "^0.3.3", + "@oceanprotocol/keeper-contracts": "^0.4.1", "@oceanprotocol/secret-store-client": "^0.0.6", "bignumber.js": "^7.2.1", "eth-crypto": "^1.2.4", diff --git a/src/keeper/ContractHandler.ts b/src/keeper/ContractHandler.ts index 07fd85f..29e3eae 100644 --- a/src/keeper/ContractHandler.ts +++ b/src/keeper/ContractHandler.ts @@ -50,7 +50,19 @@ export default class ContractHandler { const market = await ContractHandler.deployContract("OceanMarket", deployerAddress, { args: [token.options.address], }) - await ContractHandler.deployContract("ServiceAgreement", deployerAddress, {}) + + const sa = await ContractHandler.deployContract("ServiceAgreement", deployerAddress, { + args: [], + + }) + await ContractHandler.deployContract("AccessConditions", deployerAddress, { + args: [sa.options.address], + }) + + await ContractHandler.deployContract("PaymentConditions", deployerAddress, { + args: [sa.options.address, token.options.address], + }) + await ContractHandler.deployContract("DIDRegistry", deployerAddress, {}) /* not part of trilobite const dispute = await ContractHandler.deployContract("OceanDispute", deployerAddress, { diff --git a/src/keeper/contracts/ContractBase.ts b/src/keeper/contracts/ContractBase.ts index e8c92a9..d53e79a 100644 --- a/src/keeper/contracts/ContractBase.ts +++ b/src/keeper/contracts/ContractBase.ts @@ -33,7 +33,7 @@ export default abstract class ContractBase { public async getEventData(eventName: any, options: any): Promise { if (!this.contract.events[eventName]) { - throw new Error(`Event ${eventName} not found on contract ${this.contractName}`) + throw new Error(`Event "${eventName}" not found on contract "${this.contractName}"`) } return this.contract.getPastEvents(eventName, options) } @@ -42,13 +42,28 @@ export default abstract class ContractBase { return this.contract.options.address } + public getSignatureOfMethod(methodName: string): string { + + const foundMethod = this.contract.options.jsonInterface.find((method) => { + if (method.name === methodName) { + return method + } + }) + + if (!foundMethod) { + throw new Error(`Method "${methodName}" is not part of contract "${this.contractName}"`) + } + + return foundMethod.signature + } + protected async init() { this.contract = await ContractHandler.get(this.contractName) } protected async send(name: string, from: string, args: any[]): Promise { if (!this.contract.methods[name]) { - throw new Error(`Method ${name} is not part of contract ${this.contractName}`) + throw new Error(`Method "${name}" is not part of contract "${this.contractName}"`) } try { const tx = this.contract.methods[name](...args) @@ -61,7 +76,7 @@ export default abstract class ContractBase { }) } catch (err) { const argString = JSON.stringify(args, null, 2) - Logger.error(`Sending transaction ${name} on contract ${this.contractName} failed.`) + Logger.error(`Sending transaction "${name}" on contract "${this.contractName}" failed.`) Logger.error(`Args: ${argString} From: ${from}`) throw err } @@ -75,7 +90,7 @@ export default abstract class ContractBase { const method = this.contract.methods[name](...args) return method.call(from ? {from} : null) } catch (err) { - Logger.error(`Calling method ${name} on contract ${this.contractName} failed. Args: ${args}`, err) + Logger.error(`Calling method "${name}" on contract "${this.contractName}" failed. Args: ${args}`, err) throw err } } diff --git a/src/keeper/contracts/ServiceAgreement.ts b/src/keeper/contracts/ServiceAgreement.ts index 1f3a8c9..b110574 100644 --- a/src/keeper/contracts/ServiceAgreement.ts +++ b/src/keeper/contracts/ServiceAgreement.ts @@ -1,3 +1,4 @@ +import {Receipt} from "web3-utils" import ContractBase from "./ContractBase" export default class ServiceAgreement extends ContractBase { @@ -7,4 +8,26 @@ export default class ServiceAgreement extends ContractBase { await serviceAgreement.init() return serviceAgreement } + + public async setupAgreementTemplate(contractAddresses: any[], contractFunctionSignatures: string[], + depencyMatrix: number[], name: any, ownerAddress: string): Promise { + + return this.send("setupAgreementTemplate", ownerAddress, [ + contractAddresses, contractFunctionSignatures, depencyMatrix, name, + ]) + } + + public async getAgreementStatus(agreementId: string) { + + return this.call("getAgreementStatus", [agreementId]) + } + + public async executeAgreement(templateId: string, signature: string, consumerAddress: string, valueHashes: string[], + timeoutValues: number[], serviceDefinitionId: string, did: string, + publisherAddress: string): Promise { + + return this.send("executeAgreement", publisherAddress, [ + templateId, signature, consumerAddress, valueHashes, timeoutValues, serviceDefinitionId, "0x" + did, + ]) + } } diff --git a/src/keeper/contracts/conditions/AccessConditions.ts b/src/keeper/contracts/conditions/AccessConditions.ts new file mode 100644 index 0000000..973287a --- /dev/null +++ b/src/keeper/contracts/conditions/AccessConditions.ts @@ -0,0 +1,10 @@ +import ContractBase from "../ContractBase" + +export default class AccessConditions extends ContractBase { + + public static async getInstance(): Promise { + const accessConditions: AccessConditions = new AccessConditions("AccessConditions") + await accessConditions.init() + return accessConditions + } +} diff --git a/src/keeper/contracts/conditions/PaymentConditions.ts b/src/keeper/contracts/conditions/PaymentConditions.ts new file mode 100644 index 0000000..75d7960 --- /dev/null +++ b/src/keeper/contracts/conditions/PaymentConditions.ts @@ -0,0 +1,10 @@ +import ContractBase from "../ContractBase" + +export default class PaymentConditions extends ContractBase { + + public static async getInstance(): Promise { + const paymentConditions: PaymentConditions = new PaymentConditions("PaymentConditions") + await paymentConditions.init() + return paymentConditions + } +} diff --git a/src/ocean/ServiceAgreement.ts b/src/ocean/ServiceAgreement.ts new file mode 100644 index 0000000..7862d91 --- /dev/null +++ b/src/ocean/ServiceAgreement.ts @@ -0,0 +1,73 @@ +import ServiceAgreementContract from "../keeper/contracts/ServiceAgreement" +import Web3Provider from "../keeper/Web3Provider" +import Account from "./Account" +import OceanBase from "./OceanBase" +import ServiceAgreementTemplate from "./ServiceAgreementTemplate" + +export default class ServiceAgreement extends OceanBase { + + public static async executeServiceAgreement(serviceAgreementTemplate: ServiceAgreementTemplate, + did: string, consumer: Account): Promise { + + const valHashList = [ + ServiceAgreement.valueHash("bool", true), + ServiceAgreement.valueHash("bool", false), + ServiceAgreement.valueHash("uint", 120), + // asset Id: 797FD5B9045B841FDFF72 + ServiceAgreement.valueHash("string", "797FD5B9045B841FDFF72"), + ] + + const serviceDefinitionId = "0x515f158c3a5d81d15b0160cf8929916089218bdb4aa78c3ecd16633afd44b894" + + const timeoutValues = [0, 0, 0, 3] // timeout 5 blocks @ condition 4 + + const saMerkleRoot = ServiceAgreement.merkelizeServiceAgreement(serviceAgreementTemplate, valHashList, + timeoutValues, serviceDefinitionId, did) + const saMerkleRootSignature = await Web3Provider.getWeb3().eth.sign(saMerkleRoot, consumer.getId()) + + const serviceAgreement: ServiceAgreementContract = await ServiceAgreementContract.getInstance() + + const receipt = await serviceAgreement.executeAgreement( + serviceAgreementTemplate.getId(), saMerkleRootSignature, consumer.getId(), valHashList, timeoutValues, + serviceDefinitionId, did, serviceAgreementTemplate.getPublisher().getId()) + + const id = receipt.events.ExecuteAgreement.returnValues.serviceId + return new ServiceAgreement( + id, + receipt.events.ExecuteAgreement.returnValues.templateId, + receipt.events.ExecuteAgreement.returnValues.templateOwner, + receipt.events.ExecuteAgreement.returnValues.consumer, + receipt.events.ExecuteAgreement.returnValues.state, + receipt.events.ExecuteAgreement.returnValues.status, + ) + } + + protected static valueHash(type: string, value: any) { + const args = {type, value} + return Web3Provider.getWeb3().utils.soliditySha3(args).toString("hex") + } + + private static merkelizeServiceAgreement(serviceAgreementTemplate: ServiceAgreementTemplate, valueHashes: string[], + timeouts: number[], serviceDefinitionId: string, did: string) { + const args = [ + {type: "bytes32", value: serviceAgreementTemplate.getId()}, + {type: "bytes32[]", value: serviceAgreementTemplate.getConditionKeys()}, + {type: "bytes32[]", value: valueHashes}, + {type: "uint256[]", value: timeouts}, + {type: "bytes32", value: serviceDefinitionId}, + {type: "bytes32", value: did}, + ] + return Web3Provider.getWeb3().utils.soliditySha3(...args).toString("hex") + } + + private constructor(id: string, templateId: string, templateOwnerId: string, + consumerId: string, state: boolean, status: boolean) { + super(id) + } + + public async getStatus() { + const sa = await ServiceAgreementContract.getInstance() + + return sa.getAgreementStatus(this.getId()) + } +} diff --git a/src/ocean/ServiceAgreementTemplate.ts b/src/ocean/ServiceAgreementTemplate.ts new file mode 100644 index 0000000..4f14c7b --- /dev/null +++ b/src/ocean/ServiceAgreementTemplate.ts @@ -0,0 +1,67 @@ +import AccessConditions from "../keeper/contracts/conditions/AccessConditions" +import PaymentConditions from "../keeper/contracts/conditions/PaymentConditions" +import ServiceAgreement from "../keeper/contracts/ServiceAgreement" +import Web3Provider from "../keeper/Web3Provider" +import Account from "./Account" +import OceanBase from "./OceanBase" + +export default class ServiceAgreementTemplate extends OceanBase { + + public static async registerServiceAgreementsTemplate(resourceName: string, publisher: Account): + Promise { + + const paymentConditions: PaymentConditions = await PaymentConditions.getInstance() + const accessConditions: AccessConditions = await AccessConditions.getInstance() + + const contractAddresses = [ + await paymentConditions.getAddress(), + await accessConditions.getAddress(), + await paymentConditions.getAddress(), + await paymentConditions.getAddress(), + ] + const functionSignatures = [ + await paymentConditions.getSignatureOfMethod("lockPayment"), + await accessConditions.getSignatureOfMethod("grantAccess"), + await paymentConditions.getSignatureOfMethod("releasePayment"), + await paymentConditions.getSignatureOfMethod("refundPayment"), + ] + + const dependencies = [0, 1, 4, 1 | 2 ** 4 | 2 ** 5] // dependency bit | timeout bit + + const serviceAgreement: ServiceAgreement = await ServiceAgreement.getInstance() + + const receipt = await serviceAgreement.setupAgreementTemplate( + contractAddresses, functionSignatures, dependencies, + Web3Provider.getWeb3().utils.fromAscii(resourceName), publisher.getId()) + + const id = receipt.events.SetupAgreementTemplate.returnValues.serviceTemplateId + + return new ServiceAgreementTemplate( + id, + ServiceAgreementTemplate.generateConditionsKeys(id, contractAddresses, functionSignatures), + publisher) + } + + private static generateConditionsKeys(serviceAgreementTemplateId: string, contractAddresses: string[], + functionSignatures: string[]): string[] { + const conditions = [] + for (let i = 0; i < contractAddresses.length; i++) { + const types = ["bytes32", "address", "bytes4"] + const values = [serviceAgreementTemplateId, contractAddresses[i], functionSignatures[i]] + conditions.push(Web3Provider.getWeb3().utils.soliditySha3(...types, ...values).toString("hex")) + } + return conditions + } + + private constructor(id, private conditionKeys: string[], private publisher: Account) { + super(id) + } + + public getPublisher(): Account { + return this.publisher + } + + public getConditionKeys(): string[] { + return this.conditionKeys + } +} diff --git a/test/keeper/ContractBase.test.ts b/test/keeper/ContractBase.test.ts index b620dda..2506356 100644 --- a/test/keeper/ContractBase.test.ts +++ b/test/keeper/ContractBase.test.ts @@ -1,16 +1,22 @@ +import {assert} from "chai" import ConfigProvider from "../../src/ConfigProvider" import ContractHandler from "../../src/keeper/ContractHandler" +import Account from "../../src/ocean/Account" +import Ocean from "../../src/ocean/Ocean" import config from "../config" import ContractBaseMock from "../mocks/ContractBase.Mock" const wrappedContract = new ContractBaseMock("OceanToken") +let accounts: Account[] describe("ContractWrapperBase", () => { before(async () => { ConfigProvider.setConfig(config) await ContractHandler.deployContracts() - wrappedContract.initMock() + await wrappedContract.initMock() + const ocean: Ocean = await Ocean.getInstance(config) + accounts = await ocean.getAccounts() }) describe("#call()", () => { @@ -52,6 +58,29 @@ describe("ContractWrapperBase", () => { }) }) + describe("#send()", () => { + + it("should fail to call on an unknown contract function", (done) => { + + wrappedContract.sendMock("transferxxx", accounts[0].getId(), []) + .catch(() => { + + done() + }) + }) + }) + + describe("#getSignatureOfMethod()", () => { + + it("should a signature of the function", async () => { + + const sig = wrappedContract.getSignatureOfMethod("name") + assert(sig) + assert(typeof sig === "string") + assert(sig.startsWith("0x")) + }) + }) + describe("#getEventData()", () => { it("should fail on unknown event", (done) => { diff --git a/test/mocks/ContractBase.Mock.ts b/test/mocks/ContractBase.Mock.ts index 1dbef96..7d756f8 100644 --- a/test/mocks/ContractBase.Mock.ts +++ b/test/mocks/ContractBase.Mock.ts @@ -2,7 +2,7 @@ import ContractBase from "../../src/keeper/contracts/ContractBase" export default class ContractBaseMock extends ContractBase { public async initMock() { - this.init() + await this.init() } public async callMock(name: string, args: any[], from?: string) { diff --git a/test/ocean/ServiceAgreement.test.ts b/test/ocean/ServiceAgreement.test.ts new file mode 100644 index 0000000..56e6ce2 --- /dev/null +++ b/test/ocean/ServiceAgreement.test.ts @@ -0,0 +1,56 @@ +import {assert} from "chai" +import ConfigProvider from "../../src/ConfigProvider" +import ContractHandler from "../../src/keeper/ContractHandler" +import Account from "../../src/ocean/Account" +import IdGenerator from "../../src/ocean/IdGenerator" +import Ocean from "../../src/ocean/Ocean" +import ServiceAgreement from "../../src/ocean/ServiceAgreement" +import ServiceAgreementTemplate from "../../src/ocean/ServiceAgreementTemplate" +import config from "../config" +import Logger from "../../src/utils/Logger" + +let ocean: Ocean +let accounts: Account[] +let testServiceAgreementTemplate: ServiceAgreementTemplate + +describe("ServiceAgreement", () => { + + before(async () => { + ConfigProvider.setConfig(config) + await ContractHandler.deployContracts() + ocean = await Ocean.getInstance(config) + accounts = await ocean.getAccounts() + + const publisherAccount = accounts[0] + const resourceName = "superb car data" + testServiceAgreementTemplate = + await ServiceAgreementTemplate.registerServiceAgreementsTemplate(resourceName, publisherAccount) + + }) + + describe("#executeServiceAgreement()", () => { + it("should execture an service agreement", async () => { + + const consumerAccount = accounts[0] + const did: string = IdGenerator.generateId() + const serviceAgreement: ServiceAgreement = + await ServiceAgreement.executeServiceAgreement(testServiceAgreementTemplate, did, consumerAccount) + + assert(serviceAgreement) + assert(serviceAgreement.getId().startsWith("0x")) + }) + }) + + describe("#getStatus()", () => { + it("should execture an service agreement", async () => { + + const consumerAccount = accounts[0] + const did: string = IdGenerator.generateId() + const serviceAgreement: ServiceAgreement = + await ServiceAgreement.executeServiceAgreement(testServiceAgreementTemplate, did, consumerAccount) + + const status = await serviceAgreement.getStatus() + Logger.log(status) + }) + }) +}) diff --git a/test/ocean/ServiceAgreementTemplate.test.ts b/test/ocean/ServiceAgreementTemplate.test.ts new file mode 100644 index 0000000..2fc4f87 --- /dev/null +++ b/test/ocean/ServiceAgreementTemplate.test.ts @@ -0,0 +1,34 @@ +import {assert} from "chai" +import ConfigProvider from "../../src/ConfigProvider" +import ContractHandler from "../../src/keeper/ContractHandler" +import Account from "../../src/ocean/Account" +import Ocean from "../../src/ocean/Ocean" +import ServiceAgreementTemplate from "../../src/ocean/ServiceAgreementTemplate" +import config from "../config" + +let ocean: Ocean +let accounts: Account[] + +describe("ServiceAgreementTemplate", () => { + + before(async () => { + ConfigProvider.setConfig(config) + await ContractHandler.deployContracts() + ocean = await Ocean.getInstance(config) + accounts = await ocean.getAccounts() + }) + + describe("#registerServiceAgreementsTemplate()", () => { + it("should setup an agreement template", async () => { + + const publisherAccount = accounts[0] + const resourceName = "test data" + const serviceAgreementTemplate: ServiceAgreementTemplate = + await ServiceAgreementTemplate.registerServiceAgreementsTemplate(resourceName, publisherAccount) + assert(serviceAgreementTemplate) + assert(serviceAgreementTemplate.getId()) + assert(serviceAgreementTemplate.getPublisher().getId() === publisherAccount.getId()) + }) + }) + +})